Author: Denis Tokarev (Canva)
Champion: TBD
Stage: 0
Since its introduction, the object spread syntax has become a cornerstone of modern JavaScript. Its declarative nature makes it highly readable and maintainable. However, current syntax falls short when developers need to exclude keys from objects, especially in complex use cases with dynamic keys, strings with spaces, or symbols.
The key exclusion syntax introduces a way to declaratively remove keys from objects during object spread operations. It works with:
- Static keys
- Dynamic keys (e.g., values stored in variables)
- Complex key expressions
- Strings with spaces
- Symbols
The most basic use case, excluding specific keys directly in the spread syntax:
const sanitizedOpts = {
...a,
-key1,
...b,
-key2,
};
Key exclusions can also be applied in the middle of a spread, affecting only the properties accumulated so far:
const sanitizedOpts = {
...src,
...a,
-key1, // Removes 'key1' from { ...src, ...a }
...b,
};
Keys containing spaces can be excluded using either of these syntaxes:
const sanitizedOpts = {
...a,
-"key with space", // Excludes the key "key with space"
-["another key with space"], // Alternate syntax for excluding "another key with space"
...b,
};
Dynamic keys, such as those stored in variables, can also be excluded:
const sanitizedOpts = {
...a,
-[dynamicKey], // Excludes the value stored in `dynamicKey`
...b,
};
Symbols can also be excluded:
const sanitizedOpts = {
...a,
-[Symbol.for("key")], // Excludes the Symbol `Symbol.for("key")`
...b,
};
Key expressions can be computed on-the-fly:
const sanitizedOpts = {
...a,
-[dynamicPrefix + "Id"], // Removes a computed key like "userId" if `dynamicPrefix` is "user"
...b,
};
Key exclusions operate sequentially during object spreading. The syntax removes the specified keys from the accumulated result up to that point.
Input:
const sanitizedOpts = {
...src,
-key1,
...a,
};
Desugared:
const sanitizedOpts = (() => {
const _$1 = {};
for (const key in src) _$1[key] = src[key];
delete _$1.key1; // Remove from the accumulated object
for (const key in a) _$1[key] = a[key];
return _$1;
})();
Input:
const sanitizedOpts = {
...src,
...a,
-key1,
...b,
};
Desugared:
const sanitizedOpts = (() => {
const _$1 = {};
for (const key in src) _$1[key] = src[key];
for (const key in a) _$1[key] = a[key];
delete _$1.key1; // Remove from the accumulated object
for (const key in b) _$1[key] = b[key];
return _$1;
})();
Input:
const sanitizedOpts = {
...src,
-"key with space",
...a,
};
Desugared:
const sanitizedOpts = (() => {
const _$1 = {};
for (const key in src) _$1[key] = src[key];
delete _$1["key with space"]; // Remove key with space
for (const key in a) _$1[key] = a[key];
return _$1;
})();
Input:
const sanitizedOpts = {
...src,
-[Symbol.for("key")],
...a,
};
Desugared:
const sanitizedOpts = (() => {
const _$1 = {};
const excludedSymbol = Symbol.for("key");
for (const key in src) _$1[key] = src[key];
delete _$1[excludedSymbol]; // Remove symbol key
for (const key in a) _$1[key] = a[key];
return _$1;
})();
Input:
const sanitizedOpts = {
...src,
-[dynamicPrefix + "Id"],
...a,
};
Desugared:
const sanitizedOpts = (() => {
const _$1 = {};
const computedKey = dynamicPrefix + "Id";
for (const key in src) _$1[key] = src[key];
delete _$1[computedKey]; // Remove computed key
for (const key in a) _$1[key] = a[key];
return _$1;
})();
- Supports Dynamic and Complex Keys: Handles variables, strings with spaces, symbols, and expressions as keys.
- Improved Readability: Keeps code clean and declarative.
- Potential Performance Gains: May avoid unnecessary property deletions by skipping excluded keys during the copy process when feasible. In theory, this could ensure predictable Big O performance.
- Reduced Boilerplate: Simplifies exclusion patterns in complex merges.
- Smaller Bundles: Reduces the amount of code required to perform the key exclusion.
The proposed key exclusion syntax enhances the flexibility and clarity of object spread operations. Its ability to handle dynamic keys, complex expressions, strings with spaces, and symbols makes it a robust tool for JavaScript developers. By eliminating unnecessary object copies or deletions, it also provides performance benefits in large-scale applications.
Contributions and feedback are welcome!
You can exclude an array of keys using the spread operator within the exclusion syntax:
const sanitizedOpts = {
...a,
-[...keysToExclude], // Excludes all keys in the `keysToExclude` array
...b,
};
Desugared:
const sanitizedOpts = (() => {
const _$1 = {};
for (const key in src) _$1[key] = src[key];
for (const key of keysToExclude) delete _$1[key]; // Remove dynamic keys
for (const key in a) _$1[key] = a[key];
return _$1;
})();
The following is a high-level list of tasks to progress through each stage of the TC39 proposal process:
- Identified a "champion" who will advance the addition.
- Prose outlining the problem or need and the general shape of a solution.
- Illustrative examples of usage.
-
High-level API.
- [Initialspecification text.
- Transpiler support (Optional).
- Complete specification text.
- Designated reviewers have signed off on the current spec text:
- Reviewer #1 has signed off
- Reviewer #2 has signed off
- The ECMAScript editor has signed off on the current spec text.
- Test262 acceptance tests have been written for mainline usage scenarios and merged.
- Two compatible implementations which pass the acceptance tests: [1], [2].
- A pull request has been sent to tc39/ecma262 with the integrated spec text.
- The ECMAScript editor has signed off on the pull request.