Status: implemented
const { a, "%": [c, d], length } = { a: null, "%": [true, false] };
export const { x, y } = { x: 1, y: [2] };
const f = ([first, { second }], ...r) => [first, second, r];
export default [a, c, d, length, f];
A const, a module's or a body's, exported or not, and a fixed parameter may bind a pattern in place of a name: an object pattern, { p, … }, each property a shorthand name or a key and a target, a: t, "a": t, ["a"]: t, [3]: t; or an array pattern, [t, …], each item a target or an elision, [, t]; a target a name or a pattern again, and a trailing comma allowed as the literals allow one. Each name is a binding by the rule above — reserved, refused twice, exported as then, or shadowing a capture, it is refused as a name alone is — and it names the part of the value its path reads, as in JavaScript. The initializer is evaluated once, however many names read it.
A pattern is sugar over reads the language has, and that is its whole semantics: the graph outputs write the reads, const [c, d] = v; as const $0=[...v];const c=$0[0];const d=$0[1];, as a guard is written as its conditional (functions).
- An object pattern's property is the property access its key spells, with every rule of one: a
nullorundefinedsource throws, as JavaScript'sRequireObjectCoerciblethrows; a primitive source binds what it owns — a string its code units by index,const { [0]: first } = "ab";binding"a", and itslength; a number, a boolean or abigintnothing — andundefinedfor a property it does not own, asGetVreads throughToObject;lengthreads an array's, a string's or a function's; and a key a built-in prototype gives a value,toString,constructoror__proto__, is the compilation error the access is (prohibited property name), since JavaScript would read a function there and this language nothing. - An array pattern is the spread of its source and reads of the array the spread made, which is JavaScript's own reading: array destructuring runs through
GetIteratoras a spread does, so a source that is not iterable throws the sameTypeError—const [a] = {};fails here as it fails there — and a string yields its code points,const [a] = '😀';binding the two-unit string. The spread reads every element where JavaScript's destructuring stops at the last target; no program here can observe the difference, there being no generator, no side effect and no infinite iterable. - An elision,
[, b]or[a, , c], skips a position: it binds and reads nothing, and the element after it reads the next index of the spread, soconst [, b] = v;isconst $0=[...v];const b=$0[1];. One after the last element,[a, ,], is a position nothing reads, and elisions alone,const [,] = v;, bind nothing and keep the spread's failure, as the empty pattern below does. It is a pattern's alone: in a value the same comma is a hole, which no array of this language has, refused by design (array hole, arrays). - A default,
= value, may follow a name or nested target inside a pattern:{ a = 5 },{ key: a = 5 },[a = 5], or{ item: { a } = { a: 5 } }. The source property or element is read once into a hidden binding, then the selected value isread === undefined ? value : read. An absent property and an explicitlyundefinedvalue both take the default;null,false,0and''keep their value. The fallback is evaluated only when selected. Its initialization remains required even when the bound name is unused. Bindings are resolved in order:const { a = 5, b = a } = {};binds both names to5. A default that reads itself or a later binding is refused, including where an enclosing scope binds the same name. Existing capture and declaration checks still apply (defaults). - Every read is established whether or not a name reads it, as every
constis (shared values):const { a: { b } } = {};throws at load, as it throws in JavaScript. Which of a pattern's reads fails first is no observation (failure is one outcome). - An empty pattern binds nothing and keeps its failure, the check JavaScript gives a caller through it:
const [] = v;is the spread ofvalone and fails wherevis not iterable, andconst {} = v;reads one property ofv, itslength, which every kind of value may own, binding nothing — the read that fails wherevisnullorundefined, asRequireObjectCoerciblefails, and whose value is no observation. The graph outputs write the two reads,const $0=[...v];andconst $0=v.length;. An exported pattern exports each name it binds, soexport const {} = o;exports nothing, and a module whose exports all bind no name is refused, as one with no export is (exports):module exports nothing, at its lastexport. - A pattern names no function as its whole value, so no name in it is a function's own (captures and self recursion):
const { f } = { f: () => f };isconst not found. - An object pattern over a namespace,
ObjectorNumber, reads its members (built-in static functions), there being no value to read them from:const { is } = Object;isconst is = Object.is;, one entry,const { isInteger } = Number;isconst isInteger = Number.isInteger;, andconst {} = Object;reads nothing, the namespace being never nullish. A key the namespace admits is read whatever it spells, soconst { entries } = Object;isconst entries = Object.entries;thoughentriesis a prototype name. An array pattern over it, and a key it does not admit, are refused at the word (reserved word) — but a key it does not admit that is a prototype name,keysortoString, is refused first, at the key, as in any pattern (prohibited property name). - A parameter pattern is one fixed parameter, its source the argument in its position:
([a, { b }], c) => …bindsaandbfrom the first argument asconst [a, { b }] = arg;would, its reads the first entries of the function's body, so(([]) => 0)({})and(({}) => 0)(null)fail as they fail in JavaScript, whether the body is a block or an expression. It counts as one towardlengthand toward the limit of 16, as JavaScript counts it:(({ a, b }) => 0).lengthis1. Its names are parameters by every rule of one — refused twice across the list,({ a }, a) => 1, and against a body declaration — and a nested function captures them as it captures any parameter. The rest parameter keeps a plain name:(...[a]) => 1, harmless JavaScript, is not read yet and stops at its[. The head of a list is read as a value, one symbol telling no list from a group before the), so a head that is no name and no pattern is refused for the list (malformed parameter list, at its() —(1) => 2,(a.b) => 1,((a)) => 1— where a value inside the head's pattern is refused where it stands, as in aconst.
A default inside a parameter pattern does not default the argument itself: const f = ({ a = 5 }) => a; has f.length === 1, answers 5 for f({}) or f({ a: undefined }), and throws for f() or f(null). Adding a default to the whole parameter, ({ a = 5 } = {}) => a, permits the omitted argument and has length === 0. In either form a null argument still throws.
Refused, each by name, and open in destructuring: a rest element, [a, ...r] or { a, ...r } (rest element not supported); and a computed key beyond a constant — a string or a number literal alone in the brackets, { ["a"]: t } or { [3]: t }, is the constant key an access admits in brackets (property access), read by the literal's spelling as "a": t is, while { [k]: a }, { [Number(i)]: a } and a grouped literal, { [(3)]: a }, are refused (computed key in a binding pattern not supported). A value in a binding position — a literal, an access, a call, an operator, or a group, [(a)], which JavaScript admits in an assignment and refuses in a binding — is the error JavaScript names (invalid destructuring target), at the token it begins with.
See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment.
The compiler demo offers the programs destructuring in module.f.js, each with its real output.
The demo on this page runs sample.f.js, this page's example as a function of one number — an object and an array pattern in a body, and patterns and a rest in a parameter list — with every output fjs compile makes of it: the expression graph (EDAG), the Rust and the WebAssembly that Rust builds to. Its Run button calls the default export with 1 and 5, as JavaScript and as WebAssembly, and shows what each call returned or threw.
const f = ([first, { second }], ...r) => [first, second, r];
export default (...a) => {
const { x, y: [z] } = { x: a[0], y: [a[0] * 2] };
return f([x, { second: z }], 3, 4);
};