Exploring New ECMAScript Proposals: Discard Bindings, Iterator Chunking, and More
This article reviews several Stage 2 ECMAScript proposals—including discard bindings using the void operator, iterator chunking for sliding windows and non‑overlapping sequences, phase‑based ESM imports for static worker initialization, extractors for custom destructuring, and structs with shared memory, mutexes, and unsafe blocks.
Discard (void) Bindings
Proposal: proposal-discard-binding (https://github.com/tc39/proposal-discard-binding). This proposal introduces the
voidoperator as a binding that represents no value. In function calls or destructuring,
voidcan be used to ignore a value.
Example of destructuring:
<code>const { z: void, ...obj1 } = { x: 1, y: 2, z: 3 };
obj1; // { x: 1, y: 2 }</code>Function call examples:
<code>// project an array values into an array of indices
const indices = array.map((void, i) => i);
// passing a callback to Map.prototype.forEach that only cares about keys
map.forEach((void, key) => { });
// watching a specific known file for events
fs.watchFile(fileName, (void, kind) => { });
// ignoring unused parameters in an overridden method
class Logger {
log(timestamp, message) {
console.log(`${timestamp}: ${message}`);
}
}
class CustomLogger extends Logger {
log(void, message) {
// this logger doesn't use the timestamp...
}
}</code>Iterator chunking
Proposal: proposal-iterator-chunking (https://github.com/tc39/proposal-iterator-chunking). Adds a new iteration method to ECMAScript iterators that can produce overlapping (sliding window) or non‑overlapping sub‑sequences (chunking) with a configurable size.
Chunking is similar to
_.chunkfrom lodash.
<code>const digits = () => [0,1,2,3,4,5,6,7,8,9].values();
let chunksOf2 = Array.from(digits().chunks(2));
// [ [0,1], [2,3], [4,5], [6,7], [8,9] ]
let chunksOf3 = Array.from(digits().chunks(3));
// [ [0,1,2], [3,4,5], [6,7,8], [9] ]
let chunksOf4 = Array.from(digits().chunks(4));
// [ [0,1,2,3], [4,5,6,7], [8,9] ]</code>Typical use cases include pagination, grid layouts, matrix operations, formatting/encoding, and bucketing.
The proposal also supports a sliding‑window mode.
<code>let chunksOf2 = Array.from(digits().chunks(2));
let chunksOf3 = Array.from(digits().chunks(3));
let chunksOf4 = Array.from(digits().chunks(4));</code>ESM Phase Imports
Proposal: proposal-esm-phase-imports (https://github.com/tc39/proposal-esm-phase-imports), a supplement to proposal-source-phase-imports (https://github.com/tc39/proposal-source-phase-imports). It improves worker runtime and toolchain support by allowing static import of modules for Worker initialization.
Current
new Worker()requires a URL, which is not a static import and may involve relative URLs resolved against
import.meta.url, making analysis difficult.
<code>// Common case, but requires non‑trivial static analysis
const url = new URL("./my_worker.js", import.meta.url);
const worker = new Worker(url, { type: "module" });
const url2 = import.meta.resolve("./my_worker.js");
const worker2 = new Worker(url2, { type: "module" });
function createWorker(url) {
return new Worker(url, { type: "module" });
}
const processor = createWorker(url);
</code>The proposal defines a source‑phase import record so a module can be passed directly to
new Worker:
<code>import source myModule from "./my-module.js";
const worker = new Worker(myModule);
</code>Dynamic import is also supported:
<code>const workerModule = await import.source('./worker.js');
new Worker(workerModule);
</code>Extractors
Proposal: proposal-extractors (https://github.com/tc39/proposal-extractors). Introduces extractors to enhance
BindingPatternand
AssignmentPatternsyntax, allowing new forms of destructuring.
During destructuring, the extractor’s
Symbol.customMatcheris invoked to transform and return the result.
<code>function toDate(value) {
if (value instanceof Date) {
return value;
}
if (typeof value === 'string' || typeof value === 'number') {
return new Date(value);
}
return new Date();
}
const now = Date.now();
const date = toDate(now);
console.log(date.getFullYear());
</code>With extractors:
<code>const ToDateExtractor = {
[Symbol.customMatcher](value) {
if (value instanceof Date) {
return [value];
}
if (typeof value === 'string' || typeof value === 'number') {
return [new Date(value)];
}
return [new Date()];
}
};
const now = Date.now();
const ToDate(date) = now;
console.log(date.getFullYear());
</code>Supported usage examples include binding patterns and assignment patterns such as:
<code>// binding patterns
const Foo(y) = x;
const Foo([y]) = x;
const Foo({y}) = x;
const [Foo(y)] = x;
const { z: Foo(y) } = x;
const Foo(Bar(y)) = x;
const X.Foo(y) = x;
// assignment patterns
Foo(y) = x;
Foo([y]) = x;
Foo({y}) = x;
[Foo(y)] = x;
({ z: Foo(y) } = x);
Foo(Bar(y)) = x;
X.Foo(y) = x;
</code>Structs
Proposal: proposal-structs (https://github.com/tc39/proposal-structs). Introduces
Structs,
Shared Structs,
Mutex,
Condition, and
Unsafe Blocksto bring multithreading and shared memory capabilities to JavaScript, offering a higher‑performance alternative to classes.
Structs
Non‑shared structs are sealed objects (equivalent to
Object.seal(obj)) and cannot have new fields added.
<code>struct Box {
constructor(x) { this.x = x; }
x;
}
let box = new Box(0);
box.x = 42; // x is declared
assertThrows(() => { box.y = 8.8; }); // structs are sealed
assertThrows(() => { box.__proto__ = {}; }); // structs are sealed
</code>Shared Structs
Shared structs can be transferred between JavaScript agents via
MessagePort. They may only contain primitive values or other shared structs and have no instance methods or private names.
<code>// main.js
shared struct SharedBox {
x;
}
let sharedBox = new SharedBox();
let sharedBox2 = new SharedBox();
unsafe {
sharedBox.x = 42; // primitive
sharedBox.x = sharedBox2; // shared struct
assertThrows(() => { sharedBox.x = {}; }); // not shared
}
assert(Reflect.canBeShared(sharedBox2));
assert(!Reflect.canBeShared({}));
let worker = new Worker('worker.js');
worker.postMessage({ sharedBox });
unsafe {
sharedBox.x = "main";
console.log(sharedBox.x);
}
</code> <code>// worker.js
onmessage = function(e) {
let sharedBox = e.data.sharedBox;
unsafe {
sharedBox.x = "worker";
console.log(sharedBox.x);
}
};
</code>Mutex and Condition
The proposal adds
Atomics.Mutex(a non‑recursive mutex) and
Atomics.Condition(a condition variable) as shared structs with static methods for synchronization.
<code>shared struct MicrosoftSharePoint {
x;
y;
mutex;
}
let point = new MicrosoftSharePoint();
point.mutex = new Atomics.Mutex();
let worker = new Worker('worker_mutex.js');
worker.postMessage({ point });
unsafe {
using lock = Atomics.Mutex.lock(point.mutex);
point.x = "main";
point.y = "main";
}
unsafe {
using lock = Atomics.Mutex.lock(point.mutex);
console.log(point.x, point.y);
}
</code> <code>// worker_mutex.js
onmessage = function(e) {
let point = e.data.point;
unsafe {
using lock = Atomics.Mutex.lock(point.mutex);
point.x = "worker";
point.y = "worker";
}
};
</code>Unsafe Blocks
Operations on shared structs must occur inside
unsafeblocks to avoid data races.
<code>shared struct Counter {
value = 0;
}
const ctr = new Counter(); // allocation allowed
assertThrows(() => ctr.value = 1); // error (writes shared memory)
assertThrows(() => ctr.value); // error (reads shared memory)
unsafe {
ctr.value = 1; // ok
ctr.value; // ok
}
function incrementCounter(ctr, mutex) {
unsafe {
using lck = Atomics.Mutex.lock(mutex);
ctr.value++;
}
}
</code>Taobao Frontend Technology
The frontend landscape is constantly evolving, with rapid innovations across familiar languages. Like us, your understanding of the frontend is continually refreshed. Join us on Taobao, a vibrant, all‑encompassing platform, to uncover limitless potential.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.