- Ruben Bridgewater – Datadog, Node.js TSC
- Jordan Harband - HeroDevs, TC39 delegate
JavaScript objects—including Arrays, TypedArrays, and array‑like objects—can have own enumerable string‑named properties that are not numeric indices. Extracting those property names today typically falls back to verbose index‑detection filters on Object.keys:
Object.keys(arr) // Enumerable properties only
.filter(key => !(/^0|([1-9]\d*)$/.test(key) && +key < 2 ** 32 - 1));Object.getNonIndexStringProperties standardizes this task, returning an array of enumerable, non‑index string keys in the same order as Object.keys while removing hand‑rolled index checks.
This should be much much faster overall and easier to follow in code. It is needed in all cases where correctness is important.
- Python’s
list.__dict__exposes non‑index attributes but only those that are publicly accessible. - A variant returning both enumerable and non‑enumerable keys was considered, but this proposal intentionally limits itself to enumerable keys to keep semantics simple and predictable.
declare namespace Object {
/**
* Return an array of the target’s **enumerable** own property names that are
* strings and **not array indices**.
*/
function getNonIndexStringProperties(target: any): string[];
}target– Converted to an object viaToObject.- Result – An array containing the target’s own keys that:
- are of type string;
- are enumerable (
[[Enumerable]]is true); - are not array indices (
IsArrayIndexis false);
const a = [1, 2, 3];
a.description = "coords";
Object.defineProperty(a, "secret", {
value: true,
enumerable: false,
});
a[5] = 42;
Object.getNonIndexStringProperties(a);
// ["description"] ("secret" is non‑enumerable)
Object.getNonIndexStringProperties({ foo: 1, bar: 2 });
// ["foo", "bar"] (works on any object)
// TypedArray example
const ta = new Uint8Array(2);
ta.meta = "payload";
Object.getNonIndexStringProperties(ta);
// ["meta"] (excludes "0", "1" which are indices)
// Array-like example
const arrayLike = {0: 'a', 1: 'b', length: 2, note: 'x'};
Object.getNonIndexStringProperties(arrayLike);
// ["note"] (excludes "0", "1" when enumerable)Object.getNonIndexStringProperties = function getNonIndexStringProperties (target) {
function isArrayIndex(key) {
return /^0|([1-9]\d*)$/.test(key) && +key < 2 ** 32 - 1;
}
const o = Object(target);
const keys = [];
for (const key of Object.keys(o)) { // enumerable names only
if (isArrayIndex(key)) continue;
keys.push(key);
}
return keys;
};EnumerableNonIndexStringKeys ( O )
- Assert: O is an Object.
- Let keys be an empty list.
- For each element key of ?
O.[[OwnPropertyKeys]]()in list order, do- If key is a Symbol, continue.
- If IsArrayIndex(key) is true, continue.
- Let desc be ?
O.[[GetOwnProperty]](k). - If desc.[[Enumerable]] is false, continue.
- Append key to keys.
- Return CreateArrayFromList(keys).
Object.getNonIndexStringProperties ( target )
- Let O be ?
ToObject(target).- Let result be EnumerableNonIndexStringKeys(O).
- Return result.
- Arrays: A property key is an array index if it is a String whose numeric value is an integer in the inclusive range 0 … 2³²−2. Thus, for Arrays, keys like
"4294967294"are indices, while"4294967295"is not and remains a non‑index string property. - TypedArrays: Typed arrays are integer‑indexed exotic objects. Only indices in the range 0 …
length − 1exist, and they use canonical numeric index strings. Out‑of‑bounds numeric keys (e.g.,"999") are not own properties at all and cannot be created as ordinary data properties. As a result, enumeration yields only in‑range indices, which this operation excludes.
This difference means the filter excludes:
- For Arrays: all keys considered array indices by the 0 … 2³²−2 rule.
- For TypedArrays: all in‑range canonical numeric indices 0 …
length − 1(none outside that range can appear).
Locating the function on Object maximizes applicability and aligns with existing enumeration APIs (e.g., Object.keys). It:
- Works uniformly for Arrays, TypedArrays, and array‑like objects.
- Reduces conceptual friction by pairing with
Object.keys(which provides the iteration order we filter). - Avoids multiple entry points for the same operation.
For background on index semantics and related discussions, see tc39/proposal‑object‑property‑count#5.
If the committee prefers to keep the function on Array, an alternative path would be:
- Accept any
targetand applyToObjectunconditionally. - Use the same enumeration and filtering rules described in this proposal.
- Specify that the operation works on array‑like objects (and TypedArrays) as input, even though the function is reachable as
Array.getNonIndexStringProperties.
This preserves the practical benefit—supporting array‑likes and TypedArrays—albeit with slightly worse discoverability than placing it on Object.
The name is quite long, while it clearly reflects the intention. It should not matter to find an alternative, if that is deemed better, as long as the semantics stay the same.
The length property is ignored as long as it is not enumerable, which is the case for arrays and typed arrays.
The regular expression exec/match/matchAll methods produce a "match object" that is an Array, with non-index string properties on it (lastIndex, groups, etc). All of these are enumerable and would be listed.