JavaScript 2022 (ES13)

Top-level await, class fields, and more powerful features

✨ What's New in JavaScript 2022?

JavaScript 2022 (ES13) brought game-changing features like top-level await, class fields, and the at() method. These features make JavaScript more powerful and developer-friendly.


// New in 2022: Top-level await (no need for async function wrapper)
const data = await fetch('/api/users');
const users = await data.json();
console.log(users);
                                    

Key Features of ES13

Top-level Await

Use await outside async functions

// No async wrapper needed!
const data = await fetchData();
console.log(data);
🏗️

Class Fields

Define fields directly in class body

class User {
  name = 'Anonymous';
  #id = Math.random();
}
📍

Array.at() Method

Access array elements with negative indices

const arr = [1, 2, 3, 4, 5];
console.log(arr.at(-1)); // 5
console.log(arr.at(-2)); // 4
🔍

Object.hasOwn()

Better way to check object properties

const obj = { name: 'John' };
Object.hasOwn(obj, 'name'); // true

🔹 Top-level Await

Use await directly in modules without wrapping in async functions:

// Before ES13 - needed async wrapper
(async () => {
    const response = await fetch('/api/config');
    const config = await response.json();
    console.log(config);
})();

// ES13 - direct top-level await in modules
const response = await fetch('/api/config');
const config = await response.json();

// Use the config immediately
document.title = config.appName;

// Multiple awaits work too
const userResponse = await fetch('/api/user');
const user = await userResponse.json();
const settingsResponse = await fetch(`/api/settings/${user.id}`);
const settings = await settingsResponse.json();

console.log('App initialized with user:', user.name);

Output:

App initialized with user: John Doe

🔹 Class Fields & Static Blocks

Define class properties and initialization logic more elegantly:

class GameCharacter {
    // Public fields (no need for constructor)
    name = 'Unknown';
    level = 1;
    health = 100;
    
    // Private fields
    #experience = 0;
    #maxHealth = 100;
    
    // Static fields
    static totalCharacters = 0;
    static #gameVersion = '2.1.0';
    
    // Static initialization block
    static {
        console.log(`Game version: ${this.#gameVersion}`);
        this.totalCharacters = 0;
    }
    
    constructor(name) {
        this.name = name;
        GameCharacter.totalCharacters++;
    }
    
    // Private method
    #calculateLevelUp() {
        return Math.floor(this.#experience / 100);
    }
    
    gainExperience(points) {
        this.#experience += points;
        const newLevel = this.#calculateLevelUp();
        if (newLevel > this.level) {
            this.level = newLevel;
            this.health = this.#maxHealth; // Full heal on level up
            console.log(`${this.name} leveled up to ${this.level}!`);
        }
    }
    
    getStats() {
        return {
            name: this.name,
            level: this.level,
            health: this.health,
            experience: this.#experience
        };
    }
}

// Usage
const hero = new GameCharacter('Aragorn');
hero.gainExperience(150);
console.log(hero.getStats());
console.log(`Total characters: ${GameCharacter.totalCharacters}`);

Output:

Game version: 2.1.0
Aragorn leveled up to 1!
{ name: 'Aragorn', level: 1, health: 100, experience: 150 }
Total characters: 1

🔹 Array.at() Method

Access array elements with negative indices (like Python!):

const fruits = ['apple', 'banana', 'cherry', 'date', 'elderberry'];

// Old way to get last element
console.log(fruits[fruits.length - 1]); // 'elderberry'

// New way with at() - much cleaner!
console.log(fruits.at(-1)); // 'elderberry'
console.log(fruits.at(-2)); // 'date'
console.log(fruits.at(-3)); // 'cherry'

// Works with positive indices too
console.log(fruits.at(0));  // 'apple'
console.log(fruits.at(1));  // 'banana'

// Useful for dynamic access
function getLastN(array, n) {
    return array.at(-n);
}

console.log(getLastN(fruits, 1)); // 'elderberry'
console.log(getLastN(fruits, 3)); // 'cherry'

// Works with strings too!
const text = 'Hello World';
console.log(text.at(-1)); // 'd'
console.log(text.at(-6)); // 'W'

Output:

elderberry
date
cherry
apple
banana
elderberry
cherry
d
W

🔹 Object.hasOwn()

A safer way to check if an object has its own property:

const user = {
    name: 'Alice',
    age: 30,
    email: '[email protected]'
};

// Old way - can be problematic
console.log(user.hasOwnProperty('name')); // true
console.log('name' in user); // true (but includes inherited properties)

// New way - Object.hasOwn() is safer
console.log(Object.hasOwn(user, 'name')); // true
console.log(Object.hasOwn(user, 'age')); // true
console.log(Object.hasOwn(user, 'toString')); // false (inherited method)

// Why Object.hasOwn() is better:
// 1. Works even if hasOwnProperty is overridden
const problematicObj = {
    name: 'test',
    hasOwnProperty: null // Oops! Method is overridden
};

// This would throw an error:
// console.log(problematicObj.hasOwnProperty('name')); // TypeError

// This works fine:
console.log(Object.hasOwn(problematicObj, 'name')); // true

// 2. Works with objects created with null prototype
const nullProtoObj = Object.create(null);
nullProtoObj.prop = 'value';

// This would throw an error:
// console.log(nullProtoObj.hasOwnProperty('prop')); // TypeError

// This works:
console.log(Object.hasOwn(nullProtoObj, 'prop')); // true

Output:

true
true
true
true
false
true
true

🔹 RegExp Match Indices

Get the start and end positions of regex matches:

const text = 'The quick brown fox jumps over the lazy dog';
const regex = /quick|fox|dog/g;

// Add 'd' flag to get indices
const regexWithIndices = /quick|fox|dog/gd;

// Regular match
const matches = [...text.matchAll(regex)];
console.log('Regular matches:', matches[0][0]); // 'quick'

// Match with indices
const matchesWithIndices = [...text.matchAll(regexWithIndices)];
console.log('Match:', matchesWithIndices[0][0]); // 'quick'
console.log('Indices:', matchesWithIndices[0].indices[0]); // [4, 9]

// Practical example: highlighting matches
function highlightMatches(text, pattern) {
    const regex = new RegExp(pattern, 'gdi');
    const matches = [...text.matchAll(regex)];
    
    let result = text;
    let offset = 0;
    
    matches.forEach(match => {
        const [start, end] = match.indices[0];
        const before = result.slice(0, start + offset);
        const matched = result.slice(start + offset, end + offset);
        const after = result.slice(end + offset);
        
        result = before + `${matched}` + after;
        offset += 13; // Length of ''
    });
    
    return result;
}

console.log(highlightMatches(text, 'quick|fox|dog'));

Output:

Regular matches: quick
Match: quick
Indices: [4, 9]
The quick brown fox jumps over the lazy dog

🧠 Test Your Knowledge

What does array.at(-1) return?