You know nothing JS

ES 2023/2024

Before we start, never forget this mantra

Web development is difficult, only then it is fun to do. You just have to set your standards. If it were to be easy, would anyone do it?

Olawale Daniel

Goal

Explore new features from 2023 and for 2024 in ES and JS

Quick Reminder

EcmaScript is not JavaScript

(and vice-versa)

EcmaScript is not JavaScript

(and vice-versa)

EcmaScript

  • It defines a syntax with sugars
  • It defines a low-level api

JavaScript

  • It uses EcmaScript for the syntax
  • It defines a DOM api
  • It defines a Browser api

What's new in EcmaScript

Array

  • Grouping
  • Async helpers
  • New utility methods

Array & grouping

            const sequence = [1, 2, 3]; // will never change
sequence.group((n) => n%2 ? 'even' : 'odd'); // => {even: [2], odd: [1, 3]}

Array & async helper

            async function* asynGen(n) {
   for (let i = 0; i < n; ++i) { yield i * 2 }
}
const multiply = (n) => n * 2
await Array.fromAsync(asyncGen(2), multiply); // [2, 4]

Array & new utility methods

  • findLast
  • findLastIndex
  • at

Set

A sound of maths
  • intersection
  • union
  • difference
  • symmetricDifference
  • isSubsetOf
  • isSupersetOf
  • isDisjointFrom

RegExp

New operators
                            // difference/subtraction (in A but not in B)
[A--B]

// intersection (in both A and B)
[A&&B]

// nested character classes (needed to enable the above)
[A--[0-9]]
                

Weakmap

Symbols as WeakMap keys
                            const weakmap = new WeakMap();
const key = Symbol('itsKey');
const value = 'Random';
weakmap.set(key, value);
console.log(weakmap.get(key)); // Random
                

Intl

Split strings into sentences, words or graphemes with "Intl.Segmenter"
                            const segmenter = new Intl.Segmenter(
    'en', { granularity: 'sentence' // 'word', 'grapheme' }
);

console.log(
    Array.from(
        segmenter.segment(`I'm like 🫣. But you are, too! 🫵`),
        s => s.segment
    )
)// ['I'm like 🫣. ', ' But you are, too! ', '🫵']
                    

Temporal

New successor of the Date object (bye bye Moment.js)
Full documentation
                            let now = Temporal.Now.plainDate(); // To replace new Date();
let currentYear = now.getFullYear();
console.log(currentYear);
                        

Unicode formatting

                            const wellFormedStr = "Hello, World!";
const illFormedStr = "Hello, \uD800 World!";

console.log(wellFormedStr.isWellFormed()); // true 
console.log(illFormedStr.isWellFormed()); // false
console.log(wellFormedStr.toWellFormed()); // Hello, World!
console.log(illFormedStr.toWellFormed()); // Hello, � World!
                

Error

Add the optional cause property for debugging and simplify production issue analysis
                            function buildRSA(p, q) {
    if (!Number.isInteger(p) || !Number.isInteger(q)) {
        throw new Error("RSA key generation requires integer inputs.", {
        cause: { code: "NonInteger", values: [p, q] },
        });
    }
    if (!areCoprime(p, q)) {
        throw new Error("RSA key generation requires two coprime integers.", {
        cause: { code: "NonCoprime", values: [p, q] },
        });
    }
}
                

Promise

New utility methods
  • Promise.allSettled(promises) (called when all promises are done, resolved or not)
  • Promise.any(promises) (called when the first promise is resolved)
  • Promise.withResolvers() (return an object with the promise, the resolve and reject methods
                            async function readFile(file) {
   const { promise, resolve, reject } = Promise.withResolvers();
   const reader = new FileReader();
   reader.onload = resolve;
   reader.onerror = reject;
   reader.onabort = reject;
   reader.readAsText(file);
   return promise;
}
                

EcmaScript & more fluent programming

Array & non mutating methods

A sound of functional
            const sequence = [1, 2, 3]; // will never change
sequence.spliced(2, 2); // => [3]
sequence.toReversed(); // => [3, 2, 1]
sequence.toSorted(); // => [1, 2, 3]
sequence.with(2, 6); // => [1, 2, 6]

Record & Tuple

Records and Tuples can only contain primitives and other Records and Tuples. You could think of Records and Tuples as "compound primitives". By being thoroughly based on primitives, not objects, Records and Tuples are deeply immutable.

Record

                            const proposal = #{
    id: 1234,
    title: "Record & Tuple proposal",
    contents: `...`,
    // tuples are primitive types so you can put them in records:
    keywords: #["ecma", "tc39", "proposal", "record", "tuple"],
};

// Accessing keys like you would with objects!
console.log(proposal.title); // Record & Tuple proposal
console.log(proposal.keywords[1]); // tc39

// Spread like objects!
const proposal2 = #{
    ...proposal,
    title: "Stage 2: Record & Tuple",
};
console.log(proposal2.title); // Stage 2: Record & Tuple
console.log(proposal2.keywords[1]); // tc39

// Object functions work on Records:
console.log(Object.keys(proposal)); // ["contents", "id", "keywords", "title"]
                

Tuple

                            const measures = #[42, 12, 67, "measure error: foo happened"];

// Accessing indices like you would with arrays!
console.log(measures[0]); // 42
console.log(measures[3]); // measure error: foo happened

// Slice and spread like arrays!
const correctedMeasures = #[
    ...measures.slice(0, measures.length - 1),
    -1
];
console.log(correctedMeasures[0]); // 42
console.log(correctedMeasures[3]); // -1

// or use the .with() shorthand for the same result:
const correctedMeasures2 = measures.with(3, -1);
console.log(correctedMeasures2[0]); // 42
console.log(correctedMeasures2[3]); // -1

// Tuples support methods similar to Arrays
console.log(correctedMeasures2.map(x => x + 1)); // #[43, 13, 68, 0]]
                

Pipeline operator

The pipeline operator is a syntax sugar for chaining functions in a readable manner. It promises to make functional programming in JavaScript more intuitive and code more readable.
                            const double = (x) => x * 2;
const addFive = (x) => x + 5;
                                
// Before: const result = addFive(double(10));
const result = 10 |> double |> addFive; // 25
                

Observables

They are in your browser now!
(if not on chrome, do: chrome://flags/#observable-api)

Observables & Conversion

Use of Observable.from on:
  • Observable (in which case it just returns the given object)
  • AsyncIterable (anything with Symbol.asyncIterator)
  • Iterable (anything with Symbol.iterator)
  • Promise (or any thenable)

Observables & your own instance

                            const observable = new Observable((subscriber) => {
    let i = 0;
    setInterval(() => {
        if (i >= 10) subscriber.complete();
        else subscriber.next(i++);
    }, 2000);
});

observable.subscribe({
    next: console.log, // Print each value the Observable produces
    complete: console.info, // optional
    error: console.error, // optional
});
                

Observables & AbortController

                            // An observable that synchronously emits unlimited data during subscription.
let observable = new Observable((subscriber) => {
    let i = 0;
    while (true) {
        subscriber.next(i++);
    }
});

let controller = new AbortController();
observable.subscribe({
    next: (data) => {
        if (data > 100) controller.abort();
    }}, {signal: controller.signal},
});
                

Observables & Operators

Part 1
  • takeUntil (Returns an observable that mirrors the one that this method is called on, until the input observable emits its first value)
  • finally (like Promise.prototype.finally)
  • map
  • filter
  • take (takes an integer as an argument. It returns an iterator that produces, at most, the given number of elements produced by the underlying iterator)
  • drop (takes an integer as an argument. It skips the given number of elements produced by the underlying iterator before itself producing any remaining elements)

Observables & Operators

Part 2
  • flatMap
  • reduce
  • toArray
  • forEach
  • some
  • every
  • find

What's new in JavaScript

Observables & Events

Use on instead of addEventListener
                            // Filtering and mapping:
element
    .on('click')
    .filter((e) => e.target.matches('.foo'))
    .map((e) => ({ x: e.clientX, y: e.clientY }))
    .subscribe({ next: handleClickAtPoint });
                

Close Watcher API

Unified way to handle modal/side panel close (with Esc or back gesture on mobile device)
                            const watcher = new CloseWatcher();

watcher.addEventListener("cancel", () => {
    console.log("CloseWatcher cancel event");
});
watcher.addEventListener("close", () => {
    document.querySelector("#sidebar").classList.remove("open");
});
                

Page Visibility API

Api to let you know if your current document (page, iframe) is visible for the user or not
                            document.addEventListener("visibilitychange", () => {
    if (document.hidden) {
        audio.pause();
    } else {
        audio.play();
    }
});
                

Broadcast Channel API

Simple message bus that allows pub/sub semantics between windows/tabs, iframes, web workers, and service workers on the same origin

It doesn't replace the MessageChanel api (for web workers) or window.postMessage() (for windows)

Broadcast Channel API

                            // Connect to the channel named "my_bus".
const channel = new BroadcastChannel('my_bus');

// Send a message on "my_bus".
channel.postMessage('This is a test message.');
channel.postMessage(new Blob(['foo', 'bar'], {type: 'plain/text'}));
// Strings, Objects, Arrays, Blobs, ArrayBuffer, Map

// Listen for messages on "my_bus".
channel.onmessage = function(e) {
    console.log('Received', e.data);
};

// Close the channel when you're done.
channel.close();
                

Storage Bucket API

Ability to create multiple storage buckets, where the user agent may choose to delete each bucket independently of other buckets

  • More control over prioritization and organization
  • Divide and organize their data.
  • Be smart with quota usage by keeping track of quota usage per bucket

Storage Bucket API

                            const draftsBucket = await navigator.storageBuckets.open(
    "drafts", { durability: "strict", persisted: true });

const draftBlob = await draftsBucket.createBlob(
    ["Message text."], { type: "text/plain" });

const draftFile = await draftsBucket.createFile(
    ["Attachment data"], "attachment.txt",
    { type: "text/plain", lastModified: Date.now() });

// We could list history, add handlers on modifications, etc...
                

JavaScript & Modern app

Transition View API

The idea: provides a simplified way to create animations between pages and to remove the scrolling position between pages

Example

How it works?

  • We use a method to update the DOM to notify we want a transition.
  • The browser creates a state and generates a screenshot.
  • The browser creates a pseudo-element structure between the two pages.
  • The browser applies the animation.

Update DOM

                            document.startViewTransition(() => updateTheDOMSomehow(data));
// if the callback is async, then we could use an async method to render the DOM
                            
                        

Pseudo-element structure (overview)

                            ::view-transition
└─ ::view-transition-group(root)
    └─ ::view-transition-image-pair(root)
        ├─ ::view-transition-old(root)
        └─ ::view-transition-new(root);
                        

Pseudo-element structure (details)

  • ::view-transition is used to define a background color for the transition
  • ::view-transition-old(root) is a screenshot of the old view
  • ::view-transition-new(root) is a live representation of the new view
  • ::view-transition-image-pair(root) is used for the fading effect

What about ::view-transition-group(xxx)?

Usually, we have only one group: root (which represents the whole page)

However we could associate an element to a specific name and set a specific transition

(e.g. if we have the same header, we could change the content in another way)

Pseudo-element structure (overview)

                            ::view-transition
├─ ::view-transition-group(root)
│  └─ ::view-transition-image-pair(root)
│     ├─ ::view-transition-old(root)
│     └─ ::view-transition-new(root)
└─ ::view-transition-group(sidebar)
    └─ ::view-transition-image-pair(sidebar)
        ├─ ::view-transition-old(sidebar)
        └─ ::view-transition-new(sidebar)
                        

::view-transition-group(xxx) usage

                            .sidebar {
    view-transition-name: sidebar;
}

/* Entry transition */
::view-transition-new(sidebar):only-child {
  animation: 300ms cubic-bezier(0, 0, 0.2, 1) both fade-in,
    300ms cubic-bezier(0.4, 0, 0.2, 1) both slide-from-right;
}

/* Exit transition */
::view-transition-old(sidebar):only-child {
  animation: 150ms cubic-bezier(0.4, 0, 1, 1) both fade-out,
    300ms cubic-bezier(0.4, 0, 0.2, 1) both slide-to-right;
}
                        

Example

Advanced

EcmaScript - Decorators

We have now an official version to augment the behavior of classes, properties or method
It allows decorators composition and decorators with parameters
                            @requireAuth
@requireAdmin
class SampleClass {
    @trackExecution
    performAction(parameter1, parameter2) {
    // Method implementation goes here
    }

    @validateParam(0, 10)
    multiply(a, b) {
        return a * b;
    }
}
                

EcmaScript - Decorators

                            function decoratorFactory(config) {
    return function decorator(target, key, descriptor) {
        // Customize the behavior of the decorator based on the 'config' argument.
        // Modify the 'descriptor' or take other actions as needed.
        // Descriptor is the same contract used for Object.defineProperty
    };
}
                

EcmaScript - Realm

Mechanism for creating isolated JavaScript environments

Useful for secure code execution and sandboxing, allowing you to run code in a controlled and isolated context

EcmaScript - Realm

                            const igorsRealm = new Realm();
igorsRealm.evaluate('this'); // Throws a TypeError
igorsRealm.evaluate('new Array()'); // Throws a TypeError
igorsRealm.evaluate('Object.keys({})'); // Throws a TypeError

const doubleFunction = igorsRealm.evaluate('num => num * 2');
doubleFunction(10); // returns 20

const processNumber = igorsRealm.evaluate('(number, callback) => callback(number + 5)');
processNumber(5, (result => console.log(result))); // Logs 10 (5 + 5)
                

JavaScript - Web Component

To declare a Web component, we need some imperative

this is not great for server side rendering, even to build static HTML

                            const host = document.getElementById('host');
const shadowRoot = host.attachShadow({mode: 'open'});
shadowRoot.innerHTML = '

Hello Shadow DOM

';

JavaScript - Web Component

Now we have a declarative way to define a shadow dom

                            <menu-toggle>
    <template shadowrootmode="open">
        <button>
        <slot></slot>
        </button>
    </template>
    Open Menu
</menu-toggle>
                

JavaScript - Web Component

It's ease hydration, avoid flash styling and could be used for server side rendering

                            class MenuToggle extends HTMLElement {
    constructor() {
        super();

        // Detect whether we have SSR content already:
        if (this.shadowRoot) {
        // A Declarative Shadow Root exists!
        // wire up event listeners, references, etc.:
        const button = this.shadowRoot.firstElementChild;
        button.addEventListener('click', toggle);
        } else {
        // A Declarative Shadow Root doesn't exist.
        // Create a new shadow root and populate it:
        const shadow = this.attachShadow({mode: 'open'});
        shadow.innerHTML = ``;
        shadow.firstChild.addEventListener('click', toggle);
        }
    }
}

customElements.define('menu-toggle', MenuToggle);
                

Resources

Official

Google

Misc

Many thanks