WARNING: THIS SITE IS A MIRROR OF GITHUB.COM / IT CANNOT LOGIN OR REGISTER ACCOUNTS / THE CONTENTS ARE PROVIDED AS-IS / THIS SITE ASSUMES NO RESPONSIBILITY FOR ANY DISPLAYED CONTENT OR LINKS / IF YOU FOUND SOMETHING MAY NOT GOOD FOR EVERYONE, CONTACT ADMIN AT ilovescratch@foxmail.com
Skip to content

Commit ada148e

Browse files
committed
Add Jordan as champion, add many examples and more
1 parent a581861 commit ada148e

File tree

1 file changed

+138
-24
lines changed

1 file changed

+138
-24
lines changed

README.md

Lines changed: 138 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
## Status
44

5-
Champion(s): champion name(s)
5+
Champion: Jordan Harband
66

77
Author: Ruben Bridgewater <[email protected]>
88

@@ -32,6 +32,100 @@ const propCount = Object.keys(this.props).length;
3232

3333
Replacing 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

37131
Currently, 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

46144
This 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

69169
Defaults 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;
84189
Object.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
90201
let 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

93204
obj = { "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
100211
const 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
115233
2. 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:
130248
1. If Type(_target_) is not Object, throw a TypeError exception.
131249
2. If _options_ is undefined, let _options_ be an empty Object.
132250
3. 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']`.
134252
5. 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.
138256
6. Let _enumerable_ be ? Get(_options_, "enumerable").
139257
7. If _enumerable_ is undefined, set _enumerable_ to true.
140258
8. 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:
143261
11. 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
153268
12. 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

180294
Object.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

Comments
 (0)