From 97a6f0046ecff9d9c01fc73cd11f6d8147d4fede Mon Sep 17 00:00:00 2001 From: krutoo Date: Wed, 25 Feb 2026 23:04:35 +0500 Subject: [PATCH] perf(misc): keys optimized no loop over non own keys, Object.create(null) to except prototype properties, for instead for of and more --- src/misc/__test__/keys.test.ts | 20 +++++++++++++++++++ src/misc/keys.ts | 36 +++++++++++++++++++++------------- src/misc/wait.ts | 6 +++--- 3 files changed, 45 insertions(+), 17 deletions(-) diff --git a/src/misc/__test__/keys.test.ts b/src/misc/__test__/keys.test.ts index 8cc8f94..c5c1a36 100644 --- a/src/misc/__test__/keys.test.ts +++ b/src/misc/__test__/keys.test.ts @@ -32,4 +32,24 @@ describe('keys', () => { expect([...keys(a, b, c, d)]).toEqual(['name', 'age', 'sex', 'role']); }); + + test('should handle empty key properly', () => { + const a = { '': 'FooBar' }; + + expect([...keys(a)]).toEqual(['']); + }); + + test('should handle reserved keys properly', () => { + const a = { toString: 1 }; + const b = { valueOf: 2 }; + + expect([...keys(a, b)]).toEqual(['toString', 'valueOf']); + }); + + test('should handle arrays', () => { + const a = [1, 1, 1]; + const b = [2, 2, 2, 2, 2]; + + expect([...keys(a, b)]).toEqual(['0', '1', '2', '3', '4']); + }); }); diff --git a/src/misc/keys.ts b/src/misc/keys.ts index 77fa982..336ddc6 100644 --- a/src/misc/keys.ts +++ b/src/misc/keys.ts @@ -1,23 +1,31 @@ -// Using `Object.prototype.hasOwnProperty` instead of `Object.hasOwn` to support more environments -const hasOwn = Object.prototype.hasOwnProperty; - /** - * Returns generator that iterates over each unique own key of all given objects. + * Returns generator that iterates over each unique own enumerable key of all given objects. * @param objects Objects to pick keys. * @yields Every unique own key. */ export function* keys(...objects: Array>): Generator { - const map: Record = {}; + // Object.create(null) instead Set for more efficiently memory usage on small objects + const seen: Record = Object.create(null); + + // "for" instead "for of" for performance (no extra iterators) + for (let i = 0; i < objects.length; i++) { + const item = objects[i]; + + // filter spares in objects array + if (!item) continue; + + // Object.keys to get only own enumerable string keys and ignore spares + const itemKeys = Object.keys(item); + + // "for" instead "for of" for performance (no extra iterators) + for (let j = 0; j < itemKeys.length; j++) { + // non-null assertion because can guarantee that key is string here + const key = itemKeys[j]!; - for (const item of objects) { - for (const key in item) { - if (hasOwn.call(item, key)) { - if (map[key]) { - continue; - } else { - map[key] = true; - yield key; - } + // `key in seen` instead `seen[key]` for micro optimization + if (!(key in seen)) { + seen[key] = true; + yield key; } } } diff --git a/src/misc/wait.ts b/src/misc/wait.ts index 0258bd0..6ca2984 100644 --- a/src/misc/wait.ts +++ b/src/misc/wait.ts @@ -16,13 +16,13 @@ export function wait(ms: number, options: WaitOptions = {}): Promise { return Promise.reject(signal.reason); } - return new Promise((resolve, reject) => { - const timerId = setTimeout(() => resolve(), ms); + return new Promise((done, fail) => { + const timerId = setTimeout(() => done(), ms); if (signal) { const handleAbort = () => { clearTimeout(timerId); - reject(signal.reason); + fail(signal.reason); }; signal.addEventListener('abort', handleAbort, { once: true });