├── .github └── workflows │ ├── build.yml │ └── deploy.yml ├── .gitignore ├── .npmrc ├── LICENSE ├── README.md ├── package.json └── spec.emu /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build spec 2 | 3 | on: [pull_request, push] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - uses: actions/checkout@v4 11 | - uses: ljharb/actions/node/install@main 12 | name: 'nvm install lts/* && npm install' 13 | with: 14 | node-version: lts/* 15 | - run: npm run build 16 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy gh-pages 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | deploy: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v4 14 | - uses: ljharb/actions/node/install@main 15 | name: 'nvm install lts/* && npm install' 16 | with: 17 | node-version: lts/* 18 | - run: npm run build 19 | - uses: JamesIves/github-pages-deploy-action@v4 20 | with: 21 | branch: gh-pages 22 | folder: build 23 | clean: true 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | 39 | # Only apps should have lockfiles 40 | yarn.lock 41 | package-lock.json 42 | npm-shrinkwrap.json 43 | pnpm-lock.yaml 44 | 45 | # Build directory 46 | build 47 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Ruben Bridgewater 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ECMAScript Proposal: `Object.propertyCount` 2 | 3 | ## Status 4 | 5 | Champion: Jordan Harband 6 | 7 | Author: Ruben Bridgewater 8 | 9 | Stage: 0 10 | 11 | ## Overview 12 | 13 | This proposal introduces `Object.propertyCount`, a built-in method to efficiently and intuitively obtain the count of an object's own properties, with support for distinguishing among indexed properties, string-keyed properties, symbol properties, enumerable and non-enumerable properties, without the performance overhead of intermediate array allocations of the object's keys. 14 | 15 | ## Motivation 16 | 17 | Developers frequently rely on patterns like: 18 | 19 | ```js 20 | const obj = { a: 1, b: 2 }; 21 | const count = Object.keys(obj).length; 22 | ``` 23 | 24 | However, this approach creates unnecessary memory overhead and garbage collection pressure, as an intermediate array is allocated solely for counting properties. Highly-used runtimes, frameworks, and libraries (e.g., Node.js, React, Lodash, Angular, Storybook, Excalidraw, VS Code, Svelte, Next.js, three.js, Puppeteer, Tailwind, ...) frequently utilize `Object.keys(obj).length`, compounding performance issues across applications. 25 | 26 | For instance, React often counts props or state keys: 27 | 28 | ```js 29 | // React component example 30 | const propCount = Object.keys(this.props).length; 31 | ``` 32 | 33 | Replacing these patterns with a native and optimized counting method significantly reduces memory overhead, garbage collection, and as such, runtime performance impacts. 34 | 35 | ### Concrete usage examples 36 | 37 | I only searched for `Object.keys().length`, since that is the most common one. 38 | 39 | #### Angular 40 | 41 | - https://github.com/angular/angular/blob/7499b74d7d2d6db132d1b19a73e13cf6e306e41e/packages/router/src/url_tree.ts#L119 42 | - https://github.com/angular/angular/blob/7499b74d7d2d6db132d1b19a73e13cf6e306e41e/packages/core/src/transfer_state.ts#L120 43 | - https://github.com/angular/angular/blob/7499b74d7d2d6db132d1b19a73e13cf6e306e41e/packages/compiler/src/render3/view/i18n/util.ts#L57 44 | - https://github.com/angular/angular/blob/7499b74d7d2d6db132d1b19a73e13cf6e306e41e/packages/core/src/util/ng_dev_mode.ts#L97 45 | 46 | And multiple more. 47 | 48 | #### React 49 | 50 | - https://github.com/facebook/react/blob/254dc4d9f37eb512d4ee8bad6a0fae7ae491caef/packages/shared/shallowEqual.js#L33C9-L35 51 | - https://github.com/facebook/react/blob/254dc4d9f37eb512d4ee8bad6a0fae7ae491caef/packages/react-devtools-shared/src/hydration.js#L97 52 | - https://github.com/facebook/react/blob/254dc4d9f37eb512d4ee8bad6a0fae7ae491caef/packages/react-reconciler/src/ReactFiberHydrationDiffs.js#L416-L417 53 | - https://github.com/facebook/react/blob/254dc4d9f37eb512d4ee8bad6a0fae7ae491caef/packages/react-reconciler/src/ReactFiber.js#L677 54 | - https://github.com/facebook/react/blob/254dc4d9f37eb512d4ee8bad6a0fae7ae491caef/packages/react-server/src/ReactFizzServer.js#L2384 55 | - https://github.com/facebook/react/blob/254dc4d9f37eb512d4ee8bad6a0fae7ae491caef/packages/react-dom-bindings/src/client/ReactDOMComponent.js#L3138 56 | 57 | #### Node.js 58 | 59 | - https://github.com/nodejs/node/blob/c3b6f949748b49ef25b0239bd4582d29976fdbad/lib/internal/util/comparisons.js#L385 60 | - https://github.com/nodejs/node/blob/c3b6f949748b49ef25b0239bd4582d29976fdbad/lib/internal/util/comparisons.js#L754-L763 61 | - 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) 62 | - https://github.com/nodejs/node/blob/c3b6f949748b49ef25b0239bd4582d29976fdbad/lib/internal/debugger/inspect_client.js#L248 63 | - https://github.com/nodejs/node/blob/c3b6f949748b49ef25b0239bd4582d29976fdbad/lib/internal/cluster/primary.js#L146 64 | - https://github.com/nodejs/node/blob/c3b6f949748b49ef25b0239bd4582d29976fdbad/lib/internal/console/constructor.js#L537 65 | 66 | #### Minimatch 67 | 68 | https://github.com/isaacs/minimatch/blob/0569cd3373408f9d701d3aab187b3f43a24a0db7/src/index.ts#L158 69 | 70 | #### Vue 71 | 72 | - https://github.com/vuejs/core/blob/d65b25cdda4c0e7fe8b51e000ecc3696baad0492/packages/shared/src/looseEqual.ts#L36C11-L37 73 | - https://github.com/vuejs/core/blob/d65b25cdda4c0e7fe8b51e000ecc3696baad0492/rollup.config.js#L251 74 | - https://github.com/vuejs/core/blob/d65b25cdda4c0e7fe8b51e000ecc3696baad0492/packages/compiler-core/src/utils.ts#L505 75 | - https://github.com/vuejs/core/blob/d65b25cdda4c0e7fe8b51e000ecc3696baad0492/packages/runtime-core/src/customFormatter.ts#L123 76 | - https://github.com/vuejs/core/blob/d65b25cdda4c0e7fe8b51e000ecc3696baad0492/packages/compiler-sfc/src/style/pluginScoped.ts#L33 77 | - https://github.com/vuejs/core/blob/d65b25cdda4c0e7fe8b51e000ecc3696baad0492/packages/compiler-core/src/transforms/transformElement.ts#L905 78 | 79 | #### Lodash 80 | 81 | Lodash uses an own implementation that behaves as Object.keys() 82 | 83 | - https://github.com/lodash/lodash/blob/8a26eb42adb303f4adc7ef56e300f14c5992aa68/dist/lodash.js#L9921 84 | - https://github.com/lodash/lodash/blob/8a26eb42adb303f4adc7ef56e300f14c5992aa68/dist/lodash.js#L11561 85 | 86 | #### Other popular ones 87 | 88 | Almost all popular JS/TS modules make use of this pattern. 89 | 90 | - https://github.com/trekhleb/javascript-algorithms/blob/e40a67b5d1aaf006622a90e2bda60043f4f66679/src/algorithms/graph/detect-cycle/detectDirectedCycle.js#L83 91 | - https://github.com/storybookjs/storybook/blob/b91e25a25c8c1cc77ea6b316d03b4cce183d815c/code/core/src/theming/ensure.ts#L15 92 | - https://github.com/storybookjs/storybook/blob/b91e25a25c8c1cc77ea6b316d03b4cce183d815c/code/core/src/theming/ensure.ts#L15 93 | - https://github.com/storybookjs/storybook/blob/b91e25a25c8c1cc77ea6b316d03b4cce183d815c/code/lib/blocks/src/blocks/Controls.tsx#L60-L63 94 | - https://github.com/storybookjs/storybook/blob/b91e25a25c8c1cc77ea6b316d03b4cce183d815c/scripts/sandbox/templates/root.ejs#L7 95 | - https://github.com/storybookjs/storybook/blob/b91e25a25c8c1cc77ea6b316d03b4cce183d815c/code/lib/blocks/src/blocks/DocsPage.tsx#L15 96 | - https://github.com/storybookjs/storybook/blob/b91e25a25c8c1cc77ea6b316d03b4cce183d815c/code/core/assets/server/template.ejs#L67 97 | - https://github.com/tailwindlabs/tailwindcss/blob/e8715d081eac683d002892b8b3e13550f0276b45/packages/tailwindcss/src/compat/theme-variants.ts#L9 98 | - https://github.com/tailwindlabs/tailwindcss/blob/e8715d081eac683d002892b8b3e13550f0276b45/packages/%40tailwindcss-upgrade/src/migrate-postcss.ts#L346 99 | - https://github.com/tailwindlabs/tailwindcss/blob/e8715d081eac683d002892b8b3e13550f0276b45/packages/tailwindcss/src/compat/apply-compat-hooks.ts#L100 100 | - https://github.com/puppeteer/puppeteer/blob/ff74c58464f985253b0a986f5fbbe4edc1658a42/packages/puppeteer-core/src/bidi/HTTPRequest.ts#L149 101 | - https://github.com/puppeteer/puppeteer/blob/ff74c58464f985253b0a986f5fbbe4edc1658a42/packages/puppeteer-core/src/bidi/Page.ts#L623 102 | - https://github.com/excalidraw/excalidraw/blob/e1bb59fb8f115cd8e75fcaaeefa03a81b0fdc697/packages/excalidraw/actions/actionSelectAll.ts#L50 103 | - https://github.com/excalidraw/excalidraw/blob/e1bb59fb8f115cd8e75fcaaeefa03a81b0fdc697/packages/excalidraw/change.ts#L133 104 | - https://github.com/excalidraw/excalidraw/blob/e1bb59fb8f115cd8e75fcaaeefa03a81b0fdc697/packages/excalidraw/groups.ts#L38 105 | - https://github.com/excalidraw/excalidraw/blob/e1bb59fb8f115cd8e75fcaaeefa03a81b0fdc697/packages/excalidraw/components/App.tsx#L2760 106 | - https://github.com/excalidraw/excalidraw/blob/e1bb59fb8f115cd8e75fcaaeefa03a81b0fdc697/packages/excalidraw/actions/actionProperties.tsx#L1038 107 | - https://github.com/excalidraw/excalidraw/blob/e1bb59fb8f115cd8e75fcaaeefa03a81b0fdc697/packages/excalidraw/clipboard.ts#L317 108 | - https://github.com/excalidraw/excalidraw/blob/e1bb59fb8f115cd8e75fcaaeefa03a81b0fdc697/packages/excalidraw/element/mutateElement.ts#L44 109 | - https://github.com/microsoft/vscode/blob/2e6728cc3b6ab7f2bc5223dd52abb5f3b595b827/src/vs/base/common/equals.ts#L87-L91 110 | - https://github.com/microsoft/vscode/blob/2e6728cc3b6ab7f2bc5223dd52abb5f3b595b827/src/vs/platform/policy/common/policy.ts#L37 111 | - https://github.com/microsoft/vscode/blob/2e6728cc3b6ab7f2bc5223dd52abb5f3b595b827/build/lib/util.ts#L37 112 | - https://github.com/microsoft/vscode/blob/2e6728cc3b6ab7f2bc5223dd52abb5f3b595b827/src/vs/platform/policy/node/nativePolicyService.ts#L26 113 | - https://github.com/microsoft/vscode/blob/2e6728cc3b6ab7f2bc5223dd52abb5f3b595b827/extensions/terminal-suggest/src/fig/shared/utils.ts#L165 114 | - https://github.com/microsoft/vscode/blob/2e6728cc3b6ab7f2bc5223dd52abb5f3b595b827/build/lib/i18n.ts#L90 115 | - https://github.com/microsoft/vscode/blob/2e6728cc3b6ab7f2bc5223dd52abb5f3b595b827/src/vs/platform/product/common/product.ts#L61 116 | - https://github.com/sveltejs/svelte/blob/f498a21063894e6e515e62d753396410624b2e0f/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/component.js#L259 117 | - https://github.com/mrdoob/three.js/blob/b0805c2a0fd46c137d605ba098bc2b17d507b46f/src/materials/ShaderMaterial.js#L359 118 | - https://github.com/mrdoob/three.js/blob/b0805c2a0fd46c137d605ba098bc2b17d507b46f/editor/js/Sidebar.Geometry.BufferGeometry.js#L60 119 | - https://github.com/mrdoob/three.js/blob/b0805c2a0fd46c137d605ba098bc2b17d507b46f/examples/jsm/loaders/3MFLoader.js#L239 120 | - https://github.com/vercel/next.js/blob/5c5875105af06e17ccad4080a6ace137f14cdabb/packages/next/src/build/babel/loader/get-config.ts#L177 121 | - https://github.com/vercel/next.js/blob/5c5875105af06e17ccad4080a6ace137f14cdabb/turbopack/packages/devlow-bench/src/cli.ts#L34 122 | - https://github.com/vercel/next.js/blob/5c5875105af06e17ccad4080a6ace137f14cdabb/packages/next/check-error-codes.js#L31 123 | - https://github.com/vercel/next.js/blob/5c5875105af06e17ccad4080a6ace137f14cdabb/scripts/trace-dd.mjs#L67 124 | - https://github.com/vercel/next.js/blob/5c5875105af06e17ccad4080a6ace137f14cdabb/packages/next/src/server/lib/utils.ts#L243 125 | - https://github.com/vercel/next.js/blob/5c5875105af06e17ccad4080a6ace137f14cdabb/scripts/trace-to-tree.mjs#L147 126 | 127 | ## Problem Statement 128 | 129 | Currently, accurately counting object properties involves verbose and inefficient workarounds: 130 | 131 | ```js 132 | const count = [ 133 | ...Object.getOwnPropertyNames(obj), 134 | ...Object.getOwnPropertySymbols(obj) 135 | ].length; 136 | 137 | const reflectCount = Reflect.ownKeys(obj).length; 138 | 139 | assert.strictEqual(count, reflectCount); 140 | ``` 141 | 142 | This creates intermediate arrays, causing unnecessary memory usage and garbage collection, impacting application performance — especially at scale and in performance-critical code paths. 143 | 144 | 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. 145 | 146 | ## Proposed API 147 | 148 | ```js 149 | Object.propertyCount(target[, options]) 150 | ``` 151 | 152 | ### Parameters 153 | 154 | - **`target`**: The object whose properties will be counted. 155 | - Throws `TypeError` if target is not an object. 156 | - **`options`** *(optional)*: An object specifying filtering criteria: 157 | - `keyTypes`: Array specifying property types to include: 158 | - Possible values: `'index'`, `'nonIndexString'`, `'symbol'`. 159 | - Defaults to `['index', 'nonIndexString']` (aligning closely with `Object.keys`). 160 | - Throws `TypeError` if provided invalid values. 161 | - `enumerable`: Indicates property enumerability: 162 | - `true` to count only enumerable properties (default). 163 | - `false` to count only non-enumerable properties. 164 | - `'all'` to count both enumerable and non-enumerable properties. 165 | - Throws `TypeError` if provided invalid values. 166 | 167 | Defaults align closely with `Object.keys` for ease of adoption, ensuring intuitive behavior without needing explicit configuration in common cases. 168 | 169 | The naming of keyTypes and if it's an array or an object or the like is open for discussion. 170 | Important is just, that it's possible to differentiate index from non index strings somehow, as well as symbol properties. 171 | 172 | Similar applies to the enumerable option: true, false, and `'all'` seems cleanest, but it's not important how they are named. 173 | 174 | ## Detailed Examples and Edge Cases 175 | 176 | - **Empty object**: 177 | 178 | ```js 179 | Object.propertyCount({}); // returns 0 180 | ``` 181 | 182 | - **Object without prototype**: 183 | 184 | ```js 185 | const obj = Object.create(null); 186 | obj.property = 1; 187 | Object.propertyCount(obj); // returns 1 188 | ``` 189 | 190 | ```js 191 | const obj2 = { __proto__: null }); 192 | obj2.property = 1; 193 | Object.propertyCount(obj2); // returns 1 194 | ``` 195 | 196 | - **Array index keys**: 197 | 198 | See https://tc39.es/ecma262/#array-index 199 | 200 | ```js 201 | let obj = { '01': 'string key', 1: index, 2: 'index' }; 202 | Object.propertyCount(obj, { keyTypes: ['index'] }); // returns 2 203 | 204 | obj = { '0': 'index', '-1': 'string key', '01': 'string key' }; 205 | Object.propertyCount(obj, { keyTypes: ['index'] }); // returns 1 (only '0') 206 | ``` 207 | 208 | - **String based keys**: 209 | 210 | ```js 211 | const obj = { '01': 'string key', 1: 'index', 2: 'index' }; 212 | Object.propertyCount(obj, { keyTypes: ['nonIndexString'] }); // returns 1 213 | ``` 214 | 215 | - **Symbol based keys**: 216 | 217 | ```js 218 | const obj = { [Symbol()]: 'symbol', 1: 'index', 2: 'index' }; 219 | Object.propertyCount(obj, { keyTypes: ['symbol'] }); // returns 1 220 | ``` 221 | 222 | ## Explicit Semantics 223 | 224 | - Only own properties are considered. 225 | - Enumerability explicitly defined by the `enumerable` parameter. 226 | - Avoids intermediate array allocation entirely when implemented natively. 227 | 228 | ## Algorithmic Specification 229 | 230 | The native implementation should strictly avoid creating intermediate arrays or unnecessary allocations: 231 | 232 | 1. Initialize a numeric property counter to `0`. 233 | 2. Iterate directly over the object's own property descriptors 234 | - Access the internal property keys directly via the object's internal slots. 235 | - For each own property: 236 | - Determine if the key is a numeric index, a regular non-index string, or a symbol. 237 | - Check if the property type matches any specified in `keyTypes`. 238 | - If `enumerable` is not `'all'`, match the property's enumerability against the provided boolean value. 239 | - If the property meets all criteria, increment the counter. 240 | 3. Return the final count value. 241 | 242 | See the [spec proposal](./spec.emu) for details. 243 | 244 | ## Alternatives Considered 245 | 246 | - **Multiple separate methods**: Rejected due to increased cognitive load and API complexity. 247 | 248 | ## TC39 Stages and Champion 249 | 250 | - Ready for **Stage 1** (proposal) 251 | 252 | ## Use Cases 253 | 254 | - Improved readability and explicit intent 255 | - Significant performance gains 256 | - Reduced memory overhead 257 | - Simpler code 258 | 259 | ## Precedent 260 | 261 | Frequent patterns in widely-used JavaScript runtimes, frameworks, and libraries (Node.js, React, Angular, Lodash) demonstrate the common need for an optimized property counting mechanism. 262 | 263 | ## Polyfill 264 | 265 | ```js 266 | // NOTE: do not use this polyfill in a production environment 267 | const validTypes = new Set(['index', 'nonIndexString', 'symbol']); 268 | 269 | Object.propertyCount = function (target, options) { 270 | if (typeof target !== 'object' || target === null) { 271 | throw new TypeError(`Expected target to be an object. Received ${typeof target}`); 272 | } 273 | 274 | if (options === undefined) { 275 | return Object.keys(target).length; 276 | } 277 | 278 | const { keyTypes = ['index', 'nonIndexString'], enumerable = true } = options || {}; 279 | 280 | for (const type of keyTypes) { 281 | if (!validTypes.has(type)) { 282 | throw new TypeError(`Invalid property type (${type}) in 'keyTypes' option.`); 283 | } 284 | } 285 | 286 | if (typeof enumerable !== 'boolean' && enumerable !== 'all') { 287 | throw new TypeError(`Invalid input (${enumerable}) in 'enumerable' option.`); 288 | } 289 | 290 | let props = []; 291 | 292 | if (keyTypes.includes('index') || keyTypes.includes('nonIndexString')) { 293 | let stringProps = enumerable === true ? Object.keys(target) : Object.getOwnPropertyNames(target); 294 | 295 | if (!keyTypes.includes('nonIndexString')) { 296 | stringProps = stringProps.filter(key => String(parseInt(key, 10)) === key && parseInt(key, 10) >= 0); 297 | } else if (!keyTypes.includes('index')) { 298 | stringProps = stringProps.filter(key => String(parseInt(key, 10)) !== key || parseInt(key, 10) < 0); 299 | } 300 | 301 | props = stringProps; 302 | } 303 | 304 | if (keyTypes.includes('symbol')) { 305 | props = props.concat(Object.getOwnPropertySymbols(target)); 306 | } 307 | 308 | if (enumerable !== 'all') { 309 | props = props.filter(key => Object.getOwnPropertyDescriptor(target, key).enumerable === enumerable); 310 | } 311 | 312 | return props.length; 313 | }; 314 | ``` 315 | 316 | ## Considerations 317 | 318 | - **Backwards compatibility**: Fully backward compatible. 319 | - **Performance**: Native implementation will significantly outperform existing approaches by eliminating intermediate arrays. 320 | - **Flexibility**: Enumerable properties counted by default; easy inclusion/exclusion. 321 | - **Simplicity**: Improved code readability and clarity. 322 | 323 | ## Conclusion 324 | 325 | `Object.propertyCount` offers substantial performance benefits by efficiently counting object properties without intermediate arrays, enhancing ECMAScript with clarity, performance, and reduced memory overhead. 326 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "object-property-count", 4 | "description": "Proposal for a fast way to count object properties.", 5 | "scripts": { 6 | "start": "npm run build-loose -- --watch", 7 | "build": "npm run build-loose -- --strict", 8 | "build-loose": "node -e 'fs.mkdirSync(\"build\", { recursive: true })' && ecmarkup --load-biblio @tc39/ecma262-biblio --verbose spec.emu build/index.html --lint-spec" 9 | }, 10 | "homepage": "https://github.com/BridgeAR/object-property-count#readme", 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/BridgeAR/object-property-count.git" 14 | }, 15 | "license": "MIT", 16 | "devDependencies": { 17 | "@tc39/ecma262-biblio": "^2.1.2895", 18 | "ecmarkup": "^21.3.0" 19 | }, 20 | "engines": { 21 | "node": ">= 18" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /spec.emu: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
 7 | title: Object.propertyCount
 8 | stage: 0
 9 | contributors: Ruben Bridgewater, Jordan Harband
10 | 
11 | 12 | 13 |

Fundamental Objects

14 | 15 | 16 |

Object Objects

17 | 18 | 19 |

Properties of the Object Constructor

20 | 21 | 22 |

Object.propertyCount ( _target_ [ , _options_ ] )

23 |

When the `Object.propertyCount` method is called, the following steps are taken:

24 | 25 | 1. If _target_ is not an Object, throw a TypeError exception. 26 | 1. Let _resolvedOptions_ be ? GetOptionsObject(_options_). 27 | 1. Let _keyTypes_ be CreateArrayFromList(« *"index"*, *"nonIndexString"* »). 28 | 1. Let _keyTypesOption_ be ? Get(_resolvedOptions_, *"keyTypes"*). 29 | 1. If _keyTypesOption_ is not *undefined*, then 30 | 1. If _keyTypesOption_ is not an Object, throw a TypeError exception. 31 | 1. Set _keyTypes_ to ? CreateListFromArrayLike(_keyTypesOption_). 32 | 1. If _keyTypes_ contains any value other than *"index"*, *"nonIndexString"*, or *"symbol"*, or if any of those values are repeated, throw a TypeError exception. 33 | 1. Let _enumerable_ be ? Get(_resolvedOptions_, *"enumerable"*). 34 | 1. If _enumerable_ is *undefined*, set _enumerable_ to *true*. 35 | 1. If _enumerable_ is not one of *true*, *false*, or *"all"*, throw a TypeError exception. 36 | 1. Let _count_ be 0. 37 | 1. Let _ownKeys_ be _target_.[[OwnPropertyKeys]](). 38 | 1. For each element _key_ of _ownKeys_, do 39 | 1. Let _desc_ be ? _target_.[[GetOwnProperty]](_key_). 40 | 1. If _desc_ is not *undefined*, and either _enumerable_ is *"all"* or _enumerable_ is _desc_.[[Enumerable]], then 41 | 1. If _key_ is a Symbol and _keyTypes_ contains *"symbol"*, increment _count_ by 1. 42 | 1. Else if _key_ is an array index and _keyTypes_ contains *"index"*, increment _count_ by 1. 43 | 1. Else if _keyTypes_ contains *"nonIndexString"*, increment _count_ by 1. 44 | 1. Return _count_. 45 | 46 |
47 |
48 |
49 |
50 | 51 | 52 | 53 | 54 |

55 | GetOptionsObject ( 56 | _options_: an ECMAScript language value, 57 | ): either a normal completion containing an Object or a throw completion 58 |

59 |
60 |
description
61 |
62 | It returns an Object suitable for use with GetOption, either _options_ itself or a default empty Object. 63 | It throws a *TypeError* if _options_ is not *undefined* and not an Object. 64 |
65 |
66 | 67 | 1. If _options_ is *undefined*, then 68 | 1. Return OrdinaryObjectCreate(*null*). 69 | 1. If _options_ is an Object, then 70 | 1. Return _options_. 71 | 1. Throw a *TypeError* exception. 72 | 73 |
74 | --------------------------------------------------------------------------------