Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 10 additions & 66 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# ECMAScript Proposal: `Object.getOwnPropertySymbols( value, options? )`
# ECMAScript Proposal: `Object.symbols( value )`

## Status

Expand All @@ -10,13 +10,13 @@ Stage: 1

## Overview

This proposal extends `Object.getOwnPropertySymbols` with an *options object* that enables callers to filter the result list by **enumerability**.
This proposal aligns with `Object.keys` to enable callers only get **enumerable** symbols, similar to Object.keys only returning enumerable string properties.

Most symbol retrieval is in fact filtering out non-enumerable symbols.

```js
const allSymbols = Object.getOwnPropertySymbols(obj); // Current behavior (all)
const allSymbolsTwo = Object.getOwnPropertySymbols(obj, { enumerable: 'all' }); // Current behavior (all)
const enumerableSymbols = Object.getOwnPropertySymbols(obj, { enumerable: true });
const nonEnumerableSymbols = Object.getOwnPropertySymbols(obj, { enumerable: false });
const enumerableSymbols = Object.symbols(obj);
const allSymbols = Object.getOwnPropertySymbols(obj);
```

### Motivation
Expand All @@ -37,44 +37,10 @@ function enumerableSymbols(object) {

This pattern is error‑prone, incurs extra per‑property descriptor lookups, and cannot be optimized by engines. The proposed option provides an ergonomic, reliable, and performant solution.

## Detailed Design

### Add an optional second parameter

```
Object.getOwnPropertySymbols ( O [ , options ] )
```

* **`O`** – the object whose own symbol properties are to be returned.
* **`options`** – optional object with a single property:
* **`enumerable`**: `boolean | "all"` (default `"all"`)
* `true`  → return only **enumerable** own symbol properties
* `false` → return only **non‑enumerable** own symbol properties
* `"all"` → return **all** own symbol properties (current behavior)
If `options` is `undefined` or `null`, behavior is equivalent to omitting the parameter (`"all"`).

### Abstract Algorithm Changes (Wording Sketch)

1. Let *obj* be ? `ToObject(O)`.
1. Let *keys* be **?** `obj.[[OwnPropertyKeys]]()`.
1. Let *symbols* be a new empty List.
1. For each *key* of *keys*, do
1. If *key* is a Symbol, then
1. If *filter* passes for *key*, append **key** to *symbols*.
1. Return CreateArrayFromList(*symbols*).

*Filter Determination*:

* Let *mode* be the `enumerable` option value if provided, else `"all"`.
* If *mode* is `"all"`, *filter* always passes.
* Else retrieve *desc* ← ? `obj.[[GetOwnProperty]](key)`.
* If *mode* is `true`, *filter* passes iff *desc.\[\[Enumerable]]* is **true**.
* If *mode* is `false`, *filter* passes iff *desc.\[\[Enumerable]]* is **false**.

### Invariants

* Order of returned symbols **must remain** the same relative order as in `[[OwnPropertyKeys]]`.
* No new observable operations other than the necessary `[[GetOwnProperty]]` look‑ups when `mode ≠ "all"`.
* No new observable operations other than the necessary `[[GetOwnProperty]]` look‑ups.
* Accessor side effects remain as in current spec (a property descriptor retrieval can invoke user code).

## Examples
Expand All @@ -91,28 +57,19 @@ Object.getOwnPropertySymbols(obj);
Reflect.ownKeys(obj);
// [hidden, visible]

Object.getOwnPropertySymbols(obj, { enumerable: true });
Object.symbols(obj);
// [visible]

Object.getOwnPropertySymbols(obj, { enumerable: false });
// [hidden]
```

## Polyfill

```js
const original = Object.getOwnPropertySymbols;
Object.getOwnPropertySymbols ??= function getOwnPropertySymbols(O, options) {
const mode = options && Object.hasOwn(options, "enumerable")
? options.enumerable
: "all";

Object.getOwnPropertySymbols ??= function getOwnPropertySymbols(O) {
const symbols = original(O);
if (mode === "all") { return symbols; }

return symbols.filter(symbol => {
const { enumerable } = Object.getOwnPropertyDescriptor(object, symbol);
return mode ? enumerable : !enumerable;
return Object.getOwnPropertyDescriptor(object, symbol).enumerable;
});
};
```
Expand All @@ -123,10 +80,6 @@ Object.getOwnPropertySymbols ??= function getOwnPropertySymbols(O, options) {
* Behavior when `options.enumerable === "all"` exactly matches current semantics.
* Engines may optimize the enumeration filter internally, avoiding the extra descriptor allocations present in userland polyfills.

## Security & Privacy Considerations

This proposal does **not** expose new data; it merely refines selection of already‑accessible property keys. Side‑effects via accessors are unchanged from the status quo.

## Implementation Experience

A canonical polyfill (above) demonstrates feasibility; prototypes in V8 & SpiderMonkey are straightforward, as both engines filter keys during `[[OwnPropertyKeys]]` enumeration.
Expand All @@ -143,12 +96,3 @@ https://github.com/facebook/react/blob/336614679600af371b06371c0fbdd31fd9838231/

- console.log() / util.inspect()
- assert.deepStrictEqual()

## Remaining Open Questions

1. Should `enumerable: "all"` be permitted explicitly, or treated as default only?
2. Should we expose additional filters (e.g., configurable, writable) in the same options object? – Out of scope for MVP.

## References & Prior Work

`Object.propertyCount()`. It is related in a way that it applies similar performance optimizations.
7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,11 @@
},
"license": "MIT",
"devDependencies": {
"@tc39/ecma262-biblio": "^2.1.2925",
"ecmarkup": "^21.3.1"
"@tc39/ecma262-biblio": "^2.1.2997",
"ecmarkup": "^22.0.0",
"jsdom": "^27.3.0",
"parse5-html-rewriting-stream": "^8.0.0",
"tmp": "^0.2.5"
},
"engines": {
"node": ">= 18"
Expand Down
52 changes: 52 additions & 0 deletions spec.emu
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<!doctype html>
<meta charset="utf8">
<link rel="stylesheet" href="./spec.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/8.4/styles/github.min.css">
<script src="./spec.js"></script>
<pre class="metadata">
title: Object.symbols
stage: 1
contributors: Ruben Bridgewater, Jordan Harband
</pre>

<emu-clause id="sec-fundamental-objects" number="20">
<h1>Fundamental Objects</h1>

<emu-clause id="sec-object-objects" number="1">
<h1>Object Objects</h1>

<emu-clause id="sec-object-constructor" number="1">
<h1>The Object Constructor</h1>

<emu-clause id="sec-properties-of-the-object-constructor" number="2">
<h1>Properties of the Object Constructor</h1>

<emu-clause id="sec-object.seal" number="22">
<h1>Object.seal ( _O_ )</h1>
<p>This function performs the following steps when called:</p>
<emu-alg>
1. If _O_ is not an Object, return _O_.
1. Let _status_ be ? SetIntegrityLevel(_O_, ~sealed~).
1. If _status_ is *false*, throw a *TypeError* exception.
1. Return _O_.
</emu-alg>
</emu-clause>

<emu-clause id="sec-object.symbols">
<h1>Object.symbols ( _O_ )</h1>
<p>This function performs the following steps when called:</p>
<emu-alg>
1. Let _obj_ be ? ToObject(_O_).
1. Let _keys_ be ? <emu-meta effects="user-code">_obj_.[[OwnPropertyKeys]]()</emu-meta>.
1. Let _symbols_ be a new empty List.
1. For each element _key_ of _keys_, do
1. If _key_ is a Symbol, then
1. Let _desc_ be ? <emu-meta effects="user-code">_obj_.[[GetOwnProperty]]</emu-meta>.
1. If _desc_ is not *undefined* and _desc_.[[Enumerable]]_ is *true*, append _key_ to _symbols_.
1. Return CreateArrayFromList(_symbols_).
</emu-alg>
</emu-clause>
</emu-clause>
</emu-clause>
</emu-clause>
</emu-clause>