22
33## Status
44
5- Champion(s): champion name(s)
5+ Champion: Jordan Harband
66
77Author: Ruben Bridgewater
< [email protected] > 88
@@ -32,6 +32,100 @@ const propCount = Object.keys(this.props).length;
3232
3333Replacing these patterns with a native and optimized counting method significantly reduces memory overhead, garbage collection, and as such, runtime performance impacts.
3434
35+ The implementation in V8 seems quite straight forward, due to the already existing [ GetPropertyNames] ( https://github.com/v8/v8/blob/2b13b925298112a1366f721c8d30c96b8b61aeae/include/v8-object.h#L402-L405 ) API.
36+
37+ ### Concrete usage examples
38+
39+ I only searched for ` Object.keys().length ` , since that is the most common one.
40+
41+ #### Angular
42+
43+ - https://github.com/angular/angular/blob/7499b74d7d2d6db132d1b19a73e13cf6e306e41e/packages/router/src/url_tree.ts#L119
44+ - https://github.com/angular/angular/blob/7499b74d7d2d6db132d1b19a73e13cf6e306e41e/packages/core/src/transfer_state.ts#L120
45+ - https://github.com/angular/angular/blob/7499b74d7d2d6db132d1b19a73e13cf6e306e41e/packages/compiler/src/render3/view/i18n/util.ts#L57
46+ - https://github.com/angular/angular/blob/7499b74d7d2d6db132d1b19a73e13cf6e306e41e/packages/core/src/util/ng_dev_mode.ts#L97
47+
48+ And multiple more.
49+
50+ #### React
51+
52+ - https://github.com/facebook/react/blob/254dc4d9f37eb512d4ee8bad6a0fae7ae491caef/packages/shared/shallowEqual.js#L33C9-L35
53+ - https://github.com/facebook/react/blob/254dc4d9f37eb512d4ee8bad6a0fae7ae491caef/packages/react-devtools-shared/src/hydration.js#L97
54+ - https://github.com/facebook/react/blob/254dc4d9f37eb512d4ee8bad6a0fae7ae491caef/packages/react-reconciler/src/ReactFiberHydrationDiffs.js#L416-L417
55+ - https://github.com/facebook/react/blob/254dc4d9f37eb512d4ee8bad6a0fae7ae491caef/packages/react-reconciler/src/ReactFiber.js#L677
56+ - https://github.com/facebook/react/blob/254dc4d9f37eb512d4ee8bad6a0fae7ae491caef/packages/react-server/src/ReactFizzServer.js#L2384
57+ - https://github.com/facebook/react/blob/254dc4d9f37eb512d4ee8bad6a0fae7ae491caef/packages/react-dom-bindings/src/client/ReactDOMComponent.js#L3138
58+
59+ #### Node.js
60+
61+ - https://github.com/nodejs/node/blob/c3b6f949748b49ef25b0239bd4582d29976fdbad/lib/internal/util/comparisons.js#L385
62+ - https://github.com/nodejs/node/blob/c3b6f949748b49ef25b0239bd4582d29976fdbad/lib/internal/util/comparisons.js#L754-L763
63+ - https://github.com/nodejs/node/blob/c3b6f949748b49ef25b0239bd4582d29976fdbad/lib/internal/util/comparisons.js#L713-L720 (could be rewritten in a more performant way with the new API)
64+ - https://github.com/nodejs/node/blob/c3b6f949748b49ef25b0239bd4582d29976fdbad/lib/internal/debugger/inspect_client.js#L248
65+ - https://github.com/nodejs/node/blob/c3b6f949748b49ef25b0239bd4582d29976fdbad/lib/internal/cluster/primary.js#L146
66+ - https://github.com/nodejs/node/blob/c3b6f949748b49ef25b0239bd4582d29976fdbad/lib/internal/console/constructor.js#L537
67+
68+ #### Minimatch
69+
70+ https://github.com/isaacs/minimatch/blob/0569cd3373408f9d701d3aab187b3f43a24a0db7/src/index.ts#L158
71+
72+ #### Vue
73+
74+ - https://github.com/vuejs/core/blob/d65b25cdda4c0e7fe8b51e000ecc3696baad0492/packages/shared/src/looseEqual.ts#L36C11-L37
75+ - https://github.com/vuejs/core/blob/d65b25cdda4c0e7fe8b51e000ecc3696baad0492/rollup.config.js#L251
76+ - https://github.com/vuejs/core/blob/d65b25cdda4c0e7fe8b51e000ecc3696baad0492/packages/compiler-core/src/utils.ts#L505
77+ - https://github.com/vuejs/core/blob/d65b25cdda4c0e7fe8b51e000ecc3696baad0492/packages/runtime-core/src/customFormatter.ts#L123
78+ - https://github.com/vuejs/core/blob/d65b25cdda4c0e7fe8b51e000ecc3696baad0492/packages/compiler-sfc/src/style/pluginScoped.ts#L33
79+ - https://github.com/vuejs/core/blob/d65b25cdda4c0e7fe8b51e000ecc3696baad0492/packages/compiler-core/src/transforms/transformElement.ts#L905
80+
81+ #### Lodash
82+
83+ Lodash uses an own implementation that behaves as Object.keys()
84+
85+ - https://github.com/lodash/lodash/blob/8a26eb42adb303f4adc7ef56e300f14c5992aa68/dist/lodash.js#L9921
86+ - https://github.com/lodash/lodash/blob/8a26eb42adb303f4adc7ef56e300f14c5992aa68/dist/lodash.js#L11561
87+
88+ #### Other popular ones
89+
90+ Almost all popular JS/TS modules make use of this pattern.
91+
92+ - https://github.com/trekhleb/javascript-algorithms/blob/e40a67b5d1aaf006622a90e2bda60043f4f66679/src/algorithms/graph/detect-cycle/detectDirectedCycle.js#L83
93+ - https://github.com/storybookjs/storybook/blob/b91e25a25c8c1cc77ea6b316d03b4cce183d815c/code/core/src/theming/ensure.ts#L15
94+ - https://github.com/storybookjs/storybook/blob/b91e25a25c8c1cc77ea6b316d03b4cce183d815c/code/core/src/theming/ensure.ts#L15
95+ - https://github.com/storybookjs/storybook/blob/b91e25a25c8c1cc77ea6b316d03b4cce183d815c/code/lib/blocks/src/blocks/Controls.tsx#L60-L63
96+ - https://github.com/storybookjs/storybook/blob/b91e25a25c8c1cc77ea6b316d03b4cce183d815c/scripts/sandbox/templates/root.ejs#L7
97+ - https://github.com/storybookjs/storybook/blob/b91e25a25c8c1cc77ea6b316d03b4cce183d815c/code/lib/blocks/src/blocks/DocsPage.tsx#L15
98+ - https://github.com/storybookjs/storybook/blob/b91e25a25c8c1cc77ea6b316d03b4cce183d815c/code/core/assets/server/template.ejs#L67
99+ - https://github.com/tailwindlabs/tailwindcss/blob/e8715d081eac683d002892b8b3e13550f0276b45/packages/tailwindcss/src/compat/theme-variants.ts#L9
100+ - https://github.com/tailwindlabs/tailwindcss/blob/e8715d081eac683d002892b8b3e13550f0276b45/packages/%40tailwindcss-upgrade/src/migrate-postcss.ts#L346
101+ - https://github.com/tailwindlabs/tailwindcss/blob/e8715d081eac683d002892b8b3e13550f0276b45/packages/tailwindcss/src/compat/apply-compat-hooks.ts#L100
102+ - https://github.com/puppeteer/puppeteer/blob/ff74c58464f985253b0a986f5fbbe4edc1658a42/packages/puppeteer-core/src/bidi/HTTPRequest.ts#L149
103+ - https://github.com/puppeteer/puppeteer/blob/ff74c58464f985253b0a986f5fbbe4edc1658a42/packages/puppeteer-core/src/bidi/Page.ts#L623
104+ - https://github.com/excalidraw/excalidraw/blob/e1bb59fb8f115cd8e75fcaaeefa03a81b0fdc697/packages/excalidraw/actions/actionSelectAll.ts#L50
105+ - https://github.com/excalidraw/excalidraw/blob/e1bb59fb8f115cd8e75fcaaeefa03a81b0fdc697/packages/excalidraw/change.ts#L133
106+ - https://github.com/excalidraw/excalidraw/blob/e1bb59fb8f115cd8e75fcaaeefa03a81b0fdc697/packages/excalidraw/groups.ts#L38
107+ - https://github.com/excalidraw/excalidraw/blob/e1bb59fb8f115cd8e75fcaaeefa03a81b0fdc697/packages/excalidraw/components/App.tsx#L2760
108+ - https://github.com/excalidraw/excalidraw/blob/e1bb59fb8f115cd8e75fcaaeefa03a81b0fdc697/packages/excalidraw/actions/actionProperties.tsx#L1038
109+ - https://github.com/excalidraw/excalidraw/blob/e1bb59fb8f115cd8e75fcaaeefa03a81b0fdc697/packages/excalidraw/clipboard.ts#L317
110+ - https://github.com/excalidraw/excalidraw/blob/e1bb59fb8f115cd8e75fcaaeefa03a81b0fdc697/packages/excalidraw/element/mutateElement.ts#L44
111+ - https://github.com/microsoft/vscode/blob/2e6728cc3b6ab7f2bc5223dd52abb5f3b595b827/src/vs/base/common/equals.ts#L87-L91
112+ - https://github.com/microsoft/vscode/blob/2e6728cc3b6ab7f2bc5223dd52abb5f3b595b827/src/vs/platform/policy/common/policy.ts#L37
113+ - https://github.com/microsoft/vscode/blob/2e6728cc3b6ab7f2bc5223dd52abb5f3b595b827/build/lib/util.ts#L37
114+ - https://github.com/microsoft/vscode/blob/2e6728cc3b6ab7f2bc5223dd52abb5f3b595b827/src/vs/platform/policy/node/nativePolicyService.ts#L26
115+ - https://github.com/microsoft/vscode/blob/2e6728cc3b6ab7f2bc5223dd52abb5f3b595b827/extensions/terminal-suggest/src/fig/shared/utils.ts#L165
116+ - https://github.com/microsoft/vscode/blob/2e6728cc3b6ab7f2bc5223dd52abb5f3b595b827/build/lib/i18n.ts#L90
117+ - https://github.com/microsoft/vscode/blob/2e6728cc3b6ab7f2bc5223dd52abb5f3b595b827/src/vs/platform/product/common/product.ts#L61
118+ - https://github.com/sveltejs/svelte/blob/f498a21063894e6e515e62d753396410624b2e0f/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/component.js#L259
119+ - https://github.com/mrdoob/three.js/blob/b0805c2a0fd46c137d605ba098bc2b17d507b46f/src/materials/ShaderMaterial.js#L359
120+ - https://github.com/mrdoob/three.js/blob/b0805c2a0fd46c137d605ba098bc2b17d507b46f/editor/js/Sidebar.Geometry.BufferGeometry.js#L60
121+ - https://github.com/mrdoob/three.js/blob/b0805c2a0fd46c137d605ba098bc2b17d507b46f/examples/jsm/loaders/3MFLoader.js#L239
122+ - https://github.com/vercel/next.js/blob/5c5875105af06e17ccad4080a6ace137f14cdabb/packages/next/src/build/babel/loader/get-config.ts#L177
123+ - https://github.com/vercel/next.js/blob/5c5875105af06e17ccad4080a6ace137f14cdabb/turbopack/packages/devlow-bench/src/cli.ts#L34
124+ - https://github.com/vercel/next.js/blob/5c5875105af06e17ccad4080a6ace137f14cdabb/packages/next/check-error-codes.js#L31
125+ - https://github.com/vercel/next.js/blob/5c5875105af06e17ccad4080a6ace137f14cdabb/scripts/trace-dd.mjs#L67
126+ - https://github.com/vercel/next.js/blob/5c5875105af06e17ccad4080a6ace137f14cdabb/packages/next/src/server/lib/utils.ts#L243
127+ - https://github.com/vercel/next.js/blob/5c5875105af06e17ccad4080a6ace137f14cdabb/scripts/trace-to-tree.mjs#L147
128+
35129## Problem Statement
36130
37131Currently, accurately counting object properties involves verbose and inefficient workarounds:
@@ -41,10 +135,16 @@ const count = [
41135 ... Object .getOwnPropertyNames (obj),
42136 ... Object .getOwnPropertySymbols (obj)
43137].length ;
138+
139+ const reflectCount = Reflect .ownKeys (obj);
140+
141+ assert .strictEqual (count, reflectCount);
44142```
45143
46144This creates intermediate arrays, causing unnecessary memory usage and garbage collection, impacting application performance — especially at scale and in performance-critical code paths.
47145
146+ On top of that, it is also not possible to identify an array that is sparse without calling ` Object.keys() ` (or similar). This API would allow that by explicitly checking for own index properties.
147+
48148## Proposed Syntax
49149
50150``` javascript
@@ -54,11 +154,11 @@ Object.propertyCount(target[, options])
54154### Parameters
55155
56156- ** ` target ` ** : The object whose properties will be counted.
57- - Throws ` TypeError ` if target is not provided .
157+ - Throws ` TypeError ` if target is not an object .
58158- ** ` options ` ** * (optional)* : An object specifying filtering criteria:
59159 - ` keyTypes ` : Array specifying property types to include:
60- - Possible values: ` 'indices ' ` , ` 'string ' ` , ` 'symbol' ` .
61- - Defaults to ` ['indices ', 'string '] ` (aligning closely with ` Object.keys ` ).
160+ - Possible values: ` 'index ' ` , ` 'nonIndexString ' ` , ` 'symbol' ` .
161+ - Defaults to ` ['index ', 'nonIndexString '] ` (aligning closely with ` Object.keys ` ).
62162 - Throws ` TypeError ` if provided invalid values.
63163 - ` enumerable ` : Indicates property enumerability:
64164 - ` true ` to count only enumerable properties (default).
@@ -68,6 +168,11 @@ Object.propertyCount(target[, options])
68168
69169Defaults align closely with ` Object.keys ` for ease of adoption, ensuring intuitive behavior without needing explicit configuration in common cases.
70170
171+ The naming of keyTypes and if it's an array or an object or the like is open for discussion.
172+ Important is just, that it's possible to differentiate index from non index strings somehow, as well as symbol properties.
173+
174+ Similar applies to the enumerable option: true, false, and "all" seems cleanest, but it's not important how they are named.
175+
71176## Detailed Examples and Edge Cases
72177
73178- ** Empty object** :
@@ -84,21 +189,34 @@ obj.property = 1;
84189Object .propertyCount (obj); // returns 1
85190```
86191
87- - ** Numeric-like non-index (CanonicalNumericIndexString) keys** :
192+ ``` javascript
193+ Object .propertyCount ({ __proto__: null }); // returns 0
194+ ```
195+
196+ - ** Array index keys** :
197+
198+ See https://tc39.es/ecma262/#array-index
88199
89200``` javascript
90201let obj = { " 01" : " string key" , 1 : " index" , 2 : " index" };
91- Object .propertyCount (obj, { keyTypes: [' indices ' ] }); // returns 2
202+ Object .propertyCount (obj, { keyTypes: [' index ' ] }); // returns 2
92203
93204obj = { " 0" : " index" , " -1" : " string key" , " 01" : " string key" };
94- Object .propertyCount (obj, { keyTypes: [' indices ' ] }); // returns 1 (only "0")
205+ Object .propertyCount (obj, { keyTypes: [' index ' ] }); // returns 1 (only "0")
95206```
96207
97208- ** String based keys** :
98209
99210``` javascript
100211const obj = { " 01" : " string key" , 1 : " index" , 2 : " index" };
101- Object .propertyCount (obj, { keyTypes: [' string' ] }); // returns 1
212+ Object .propertyCount (obj, { keyTypes: [' nonIndexString' ] }); // returns 1
213+ ```
214+
215+ - ** Symbol based keys** :
216+
217+ ``` javascript
218+ const obj = { [Symbol ()]: " symbol" , 1 : " index" , 2 : " index" };
219+ Object .propertyCount (obj, { keyTypes: [' symbol' ] }); // returns 1
102220```
103221
104222## Explicit Semantics
@@ -115,7 +233,7 @@ The native implementation should strictly avoid creating intermediate arrays or
1152332 . Iterate directly over the object's own property descriptors
116234 - Access the internal property keys directly via the object's internal slots.
117235 - For each own property:
118- - Determine if the key is a numeric index, a regular string, or a symbol.
236+ - Determine if the key is a numeric index, a regular non-index string, or a symbol.
119237 - Check if the property type matches any specified in ` keyTypes ` .
120238 - If ` enumerable ` is not ` 'all' ` , match the property's enumerability against the provided boolean value.
121239 - If the property meets all criteria, increment the counter.
@@ -130,11 +248,11 @@ When the `propertyCount` method is called, the following steps are taken:
1302481 . If Type(_ target_ ) is not Object, throw a TypeError exception.
1312492 . If _ options_ is undefined, let _ options_ be an empty Object.
1322503 . Let _ keyTypes_ be ? Get(_ options_ , "keyTypes").
133- 4 . If _ keyTypes_ is undefined, set _ keyTypes_ to the array ` ['indices ', 'string '] ` .
251+ 4 . If _ keyTypes_ is undefined, set _ keyTypes_ to the array ` ['index ', 'nonIndexString '] ` .
1342525 . Else, perform the following:
135253 a. If Type(_ keyTypes_ ) is not Object, throw a TypeError exception.
136254 b. Set _ keyTypes_ to an internal List whose elements are the String values of the elements of _ keyTypes_ .
137- c. If _ keyTypes_ contains any value other than "indices ", "string ", or "symbol", throw a TypeError exception.
255+ c. If _ keyTypes_ contains any value other than "index ", "nonIndexString ", or "symbol", throw a TypeError exception.
1382566 . Let _ enumerable_ be ? Get(_ options_ , "enumerable").
1392577 . If _ enumerable_ is undefined, set _ enumerable_ to true.
1402588 . Else if _ enumerable_ is not one of true, false, or "all", throw a TypeError exception.
@@ -143,13 +261,10 @@ When the `propertyCount` method is called, the following steps are taken:
14326111 . For each element _ key_ of _ ownKeys_ , perform the following steps:
144262 a. Let _ desc_ be ? OrdinaryGetOwnProperty(_ target_ , _ key_ ).
145263 b. If _ enumerable_ is not 'all'
146- i. If _ enumerable_ is true and _ desc_ .[[ Enumerable]] is false, continue to the next _ key_ .
147- ii. If _ enumerable_ is false and _ desc_ .[[ Enumerable]] is true, continue to the next _ key_ .
264+ i. If _ enumerable_ is unequal to _ desc_ .[[ Enumerable]] , continue to the next _ key_ .
148265 c. If Type(_ key_ ) is Symbol and "symbol" is present in _ keyTypes_ , increment _ count_ by 1.
149- d. Else if Type(_ key_ ) is String, perform:
150- i. Let _ numericIndex_ be ! CanonicalNumericIndexString(_ key_ ).
151- ii. If _ numericIndex_ is not undefined and "indices" is present in _ keyTypes_ , increment _ count_ by 1.
152- iii. Else if _ numericIndex_ is undefined and "string" is present in _ keyTypes_ , increment _ count_ by 1.
266+ d. Else if Type(_ key_ ) is array index and "index" is present in _ keyTypes_ , increment _ count_ by 1.
267+ e. Else if "nonIndexString" is present in _ keyTypes_ , increment _ count_ by 1
15326812 . Return _ count_ .
154269
155270## Alternatives Considered
@@ -159,7 +274,6 @@ When the `propertyCount` method is called, the following steps are taken:
159274## TC39 Stages and Champion
160275
161276- Ready for ** Stage 1** (proposal)
162- - Champion(s) and community stakeholders pending
163277
164278## Use Cases
165279
@@ -175,10 +289,10 @@ Frequent patterns in widely-used JavaScript runtimes, frameworks, and libraries
175289## Polyfill
176290
177291``` javascript
178- const validTypes = new Set ([' indices ' , ' string ' , ' symbol' ]);
292+ const validTypes = new Set ([' index ' , ' nonIndexString ' , ' symbol' ]);
179293
180294Object .propertyCount = function (target , options = {}) {
181- const { keyTypes = [' indices ' , ' string ' ], enumerable = true } = options;
295+ const { keyTypes = [' index ' , ' nonIndexString ' ], enumerable = true } = options;
182296
183297 for (const type of keyTypes) {
184298 if (! validTypes .has (type)) {
@@ -192,14 +306,14 @@ Object.propertyCount = function(target, options = {}) {
192306
193307 let props = [];
194308
195- if (keyTypes .includes (' indices ' ) || keyTypes .includes (' string ' )) {
309+ if (keyTypes .includes (' index ' ) || keyTypes .includes (' nonIndexString ' )) {
196310 let stringProps = Object .getOwnPropertyNames (target);
197311
198- if (! keyTypes .includes (' string ' )) {
312+ if (! keyTypes .includes (' nonIndexString ' )) {
199313 stringProps = stringProps .filter (key => String (parseInt (key, 10 )) === key && parseInt (key, 10 ) >= 0 );
200314 }
201315
202- if (! keyTypes .includes (' indices ' )) {
316+ if (! keyTypes .includes (' index ' )) {
203317 stringProps = stringProps .filter (key => String (parseInt (key, 10 )) !== key || parseInt (key, 10 ) < 0 );
204318 }
205319
@@ -227,4 +341,4 @@ Object.propertyCount = function(target, options = {}) {
227341
228342## Conclusion
229343
230- ` Object.propertyCount ` offers substantial performance benefits by efficiently counting object properties without intermediate arrays, enhancing ECMAScript with clarity, performance, and reduced memory overhead.
344+ ` Object.propertyCount ` offers substantial performance benefits by efficiently counting object properties without intermediate arrays, enhancing ECMAScript with clarity, performance, and reduced memory overhead.
0 commit comments