Multipage preference

Stage 1 Draft / August 7, 2026

Composites

Composites

This proposal introduces built-in composites: frozen, ordinary objects with a null prototype that are interned, so that two composites created from the same set of named values are the same object (identical under SameValue, and therefore under ===).

Because composites are interned, they can be used as Map keys and Set values with value-based equality without any change to Map, Set, or the equality operators:

const pos1 = Composite({ x: 1, y: 4 });
const pos2 = Composite({ x: 1, y: 4 });
pos1 === pos2; // true

const positions = new Set();
positions.add(pos1);
positions.has(pos2); // true
Note

This section is non-normative.

A composite:

  • is an Object (typeof Composite({}) is "object");
  • has a null prototype and is not created with new;
  • is frozen, and all of its properties are enumerable;
  • has only String keys, presented in a canonical order that does not depend on the order they were provided;
  • cannot be held weakly (see 4.1).

1 Well-Known Intrinsic Objects

The following entry is added to Table 6 (Well-Known Intrinsic Objects):

Table 1: Well-Known Intrinsic Objects (additions)
Intrinsic Name Global Name ECMAScript Language Association
%Composite% Composite The Composite function (2.1)

2 Composite Objects

A composite object is an ordinary object that is contained in the surrounding agent's Composite Registry (3.7). Composite objects are only created by FindOrCreateComposite (3.5), which is the only operation that adds an object to the Composite Registry. An object's status as a composite object cannot be forged: because only membership in the Composite Registry matters, an object such as a Proxy exotic object whose target is a composite object is not itself a composite object.

Every composite object has a [[Prototype]] of null, has [[Extensible]] set to false, and has only own data properties whose [[Writable]] and [[Configurable]] attributes are false and whose [[Enumerable]] attribute is true. All keys of a composite object are Strings.

Composite objects are interned: within an agent, there is at most one composite object for any given set of key/value pairs (see 3.7). Two composite objects that would have the same key/value pairs are therefore the same object.

2.1 The Composite Function

The Composite function:

  • is %Composite%.
  • is the initial value of the "Composite" property of the global object.
  • returns a composite object when called as a function, creating one only if no interned composite with the same key/value pairs already exists.
  • does not have a [[Construct]] internal method; it cannot be used as a constructor with the new operator.
  • does not have a "prototype" property.

2.1.1 Composite ( arg [ , options ] )

This function performs the following steps when called:

  1. If arg is not an Object, throw a TypeError exception.
  2. If arg is a composite object, return arg.
  3. Let preserveNegativeZero be false.
  4. If options is not undefined, then
    1. Let preserveNegativeZeroOption be ? Get(options, "preserveNegativeZero").
    2. Set preserveNegativeZero to ToBoolean(preserveNegativeZeroOption).
  5. Let entries be a new empty List.
  6. Let keys be ? arg.[[OwnPropertyKeys]]().
  7. For each element key of keys, do
    1. Let desc be ? arg.[[GetOwnProperty]](key).
    2. If desc is not undefined and desc.[[Enumerable]] is true, then
      1. If key is a Symbol, throw a TypeError exception.
      2. Let value be ? Get(arg, key).
      3. Set value to CanonicalizeCompositeValue(value, preserveNegativeZero).
      4. Append the Composite Entry Record { [[Key]]: key, [[Value]]: value } to entries.
  8. Return FindOrCreateComposite(entries).
Note 1

Values are read from arg in the order the keys are reported by arg.[[OwnPropertyKeys]](). Getters are therefore invoked exactly once each, eagerly, during this operation. The order in which the resulting properties appear on the composite object does not depend on this order; see FindOrCreateComposite (3.5).

Note 2

Only own enumerable properties of arg contribute to the composite; inherited and non-enumerable properties are ignored. An own enumerable Symbol-keyed property throws a TypeError; a non-enumerable one is ignored.

Note 3

If arg is itself a composite object, the result is a composite object with the same key/value pairs, which by interning is arg itself. The options argument does not change the result.

Note 4

By default, the Composite function normalizes -0𝔽 to +0𝔽. If the options object provides a truthy preserveNegativeZero property, then -0𝔽 is preserved instead.

2.2 Properties of the Composite Function

The Composite function:

  • has a [[Prototype]] internal slot whose value is %Function.prototype%.
  • has the following properties:

2.2.1 Composite.isComposite ( arg )

This function performs the following steps when called:

  1. Return IsComposite(arg).

3 Abstract Operations for Composite Objects

3.1 IsComposite ( value )

The abstract operation IsComposite takes argument value (an ECMAScript language value) and returns a Boolean. It returns true if value is a composite object. It performs the following steps when called:

  1. If value is not an Object, return false.
  2. If the surrounding agent's Composite Registry contains value, return true.
  3. Return false.
Note

This operation is defined in terms of searching the Composite Registry, but an implementation is free to use any faster mechanism that avoids such a search as long as the observable behaviour is equivalent.

3.2 CanonicalizeCompositeValue ( value, preserveNegativeZero )

The abstract operation CanonicalizeCompositeValue takes arguments value (an ECMAScript language value) and preserveNegativeZero (a Boolean) and returns an ECMAScript language value. It returns a canonical representative of value. It performs the following steps when called:

  1. If value is -0𝔽 and preserveNegativeZero is false, return +0𝔽.
  2. If value is NaN, return an implementation chosen NaN value. An implementation must return the same NaN value for every NaN argument, so that all NaN values returned from this operations are not implementation distinguishable (see NumericToRawBytes) from each other.
  3. Return value.
Note

This specification recognizes a single NaN value, so step two is observable only where an implementation preserves distinct NaN bit patterns (see NumericToRawBytes). Without this step, comparing the bytes of a returned composite's NaN against those supplied could reveal whether the composite was newly created or already interned, exposing information about the internal registry; canonicalizing NaN keeps that status private.

3.3 Composite Entry Records

A Composite Entry Record is a Record value used to describe a single key/value pair of a composite object.

Composite Entry Records have the fields listed in Table 2.

Table 2: Composite Entry Record Fields
Field Name Value Meaning
[[Key]] a String The property key.
[[Value]] an ECMAScript language value The property value.

3.4 CompositeCanonicalOrder ( entries )

The abstract operation CompositeCanonicalOrder takes argument entries (a List of Composite Entry Records) and returns a List of Composite Entry Records. It returns entries ordered into the canonical key order used by composite objects. This order does not depend on the order in which the entries were provided, giving composites a canonical form. It performs the following steps when called:

  1. Let integerEntries be a new empty List.
  2. Let stringEntries be a new empty List.
  3. For each Composite Entry Record entry of entries, do
    1. Assert: entry.[[Key]] is a String.
    2. If entry.[[Key]] is an array index, append entry to integerEntries; otherwise, append entry to stringEntries.
  4. Sort integerEntries into ascending order according to the numeric values of the array indices denoted by their [[Key]] fields.
  5. Sort stringEntries into ascending order according to the lexicographic order of their [[Key]] fields, comparing them by code unit.
  6. Return the list-concatenation of integerEntries and stringEntries.
Note

This is the same key order ordinary objects use for their own String keys (array-index keys in ascending numeric order, then the rest), except that composites order the remaining String keys lexicographically rather than by creation order.

3.5 FindOrCreateComposite ( entries )

The abstract operation FindOrCreateComposite takes argument entries (a List of Composite Entry Records) and returns a composite object. It returns the interned composite object for the key/value pairs described by entries, creating it if it does not already exist. It is assumed that no two elements of entries have the same [[Key]], and that each element's [[Value]] is already the result of CanonicalizeCompositeValue (3.2). It performs the following steps when called:

  1. Let sortedEntries be CompositeCanonicalOrder(entries).
  2. Let registry be the surrounding agent's Composite Registry.
  3. For each composite object c of registry, do
    1. If CompositeMatch(c, sortedEntries) is true, return c.
  4. Let c be OrdinaryObjectCreate(null).
  5. For each Composite Entry Record entry of sortedEntries, do
    1. Let desc be the PropertyDescriptor { [[Value]]: entry.[[Value]], [[Writable]]: false, [[Enumerable]]: true, [[Configurable]]: false }.
    2. Perform ! DefinePropertyOrThrow(c, entry.[[Key]], desc).
  6. Set c.[[Extensible]] to false.
  7. Add c to registry.
  8. Return c.
Note

The search over registry is only intended to describe the required observable result of interning; it is not intended to be a viable implementation model. Interning must be implemented using either hash tables or other mechanisms that, on average, provide interning times that are sublinear on the number of composite objects in the registry.

3.6 CompositeMatch ( c, sortedEntries )

The abstract operation CompositeMatch takes arguments c (a composite object) and sortedEntries (a List of Composite Entry Records) and returns a Boolean. It returns true if the existing composite object c has exactly the key/value pairs described by sortedEntries. It performs the following steps when called:

  1. Assert: sortedEntries is in the canonical key order produced by CompositeCanonicalOrder (3.4).
  2. Let keys be ! c.[[OwnPropertyKeys]]().
  3. Assert: keys is in the canonical key order produced by CompositeCanonicalOrder (3.4).
  4. If the number of elements in keys is not equal to the number of elements in sortedEntries, return false.
  5. Let i be 0.
  6. For each Composite Entry Record entry of sortedEntries, do
    1. Let key be keys[i].
    2. If entry.[[Key]] is not key, return false.
    3. Let current be ! Get(c, key).
    4. If SameValue(current, entry.[[Value]]) is false, return false.
    5. Set i to i + 1.
  7. Return true.

3.7 The Composite Registry

Each agent has an associated Composite Registry: a List of Objects, initially empty. The objects it contains are exactly the composite objects of that agent (2). Objects are added to it only by FindOrCreateComposite (3.5) and are shared across all realms within the agent.

Note 1

Membership in the registry does not keep a composite object alive; an implementation may remove one once it is no longer live. This removal is unobservable, since composite objects cannot be held weakly (see 4.1) and so cannot be placed in a WeakMap, WeakRef, WeakSet, or FinalizationRegistry.

Note 2

The Composite Registry is per-agent, not per-realm. As a result, composites are equal across realms within the same agent (for example, across same-origin iframes), similar to symbols obtained from Symbol.for. A composite object created via one realm's Composite function has a [[Prototype]] of null and so bears no relationship to the realm that created it; only the key/value pairs matter.

4 Modifications to Existing Abstract Operations

4.1 CanBeHeldWeakly ( v )

The abstract operation CanBeHeldWeakly takes argument v (an ECMAScript language value) and returns a Boolean. It returns true if and only if v is suitable to be used as a key of a WeakMap, added to a WeakSet, used as the target or unregister token of a FinalizationRegistry, or held by a WeakRef. Composite objects are excluded because they are interned: holding one weakly would, counter-intuitively, keep it alive in the registry, which could leak memory. It performs the following steps when called:

  1. If v is an Object and IsComposite(v) is false, return true.
  2. If v is a Symbol and KeyForSymbol(v) is undefined, return true.
  3. Return false.
Note

Because WeakMap, WeakSet, WeakRef, and FinalizationRegistry all gate their arguments through CanBeHeldWeakly, this single change causes each of them to reject composite objects with a TypeError.

Copyright & Software License

Software License

All Software contained in this document ("Software") is protected by copyright and is being made available under the "BSD License", included below. This Software may be subject to third party rights (rights from parties other than Ecma International), including patent rights, and no licenses under such third party rights are granted under this license even if the third party concerned is a member of Ecma International. SEE THE ECMA CODE OF CONDUCT IN PATENT MATTERS AVAILABLE AT https://ecma-international.org/memento/codeofconduct.htm FOR INFORMATION REGARDING THE LICENSING OF PATENT CLAIMS THAT ARE REQUIRED TO IMPLEMENT ECMA INTERNATIONAL STANDARDS.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

  1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
  2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
  3. Neither the name of the authors nor Ecma International may be used to endorse or promote products derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE ECMA INTERNATIONAL "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ECMA INTERNATIONAL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.