Skip to content

Commit bac20c7

Browse files
authored
Update README.md
1 parent 7d88855 commit bac20c7

File tree

1 file changed

+113
-32
lines changed

1 file changed

+113
-32
lines changed

README.md

Lines changed: 113 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,137 @@
1-
# proposal-plus-minus-spread
1+
# Key exclusion and inclusion syntax in object spread
22

3-
Minus and plus operators: a spread operator enhancement proposal for JavaScript / ECMAScript
3+
ECMAScript proposal and reference implementation for exclusion and inclusion syntax in the object spread.
4+
5+
**Author(s):** Denis Tokarev (Canva)
6+
7+
**Champion:** not identified
8+
9+
**Stage:** 0
410

511

612
## Motivation
713

8-
Since the object spread operator spec had landed, it has become widely used by devs. The reason for that is quite obvious – it's declarative, easily readable and straightforward way to produce an object from other objects. However, there are still use cases when devs have to write imperative code for performing quite simple operations. An example of such operation is filtering out an item from an object by key.
14+
Since its introduction to the specification, [the object spread syntax](https://github.com/tc39/proposal-object-rest-spread) has gained extreme popularity in the codebases of most organizations and open-source projects. Being declarative, the object spread syntax is easy to use, read, understand, and maintain.
15+
16+
However, when the use case is slightly more complex than just merging a few objects, the developers don't have the luxury of writing declarative code.
917

10-
Let's illustrate this wth code. Let's assume we've got an object `obj`:
18+
Perhaps the most popular example is removing a key from the result object:
1119

1220
```js
13-
const obj = {
14-
a: 1,
15-
b: {
16-
b1: 21,
17-
b2: 22,
18-
},
19-
c: 3,
21+
// When the key name is known statically
22+
const sanitizedOpts = (opts) => {
23+
const result = {
24+
...PRIVATE_OPTS,
25+
..opts,
26+
};
27+
28+
// Removing the key "keyThatMustNotBeThere" from the result
29+
delete result.keyThatMustNotBeThere;
30+
31+
return result;
2032
};
21-
```
2233

23-
If we want to take out an item from `obj.b` by its key (eg. `b1`) using a declarative paradigm, we have to write something like that (let's write it as a function, to cover a broader case when it does something meaningful before returning a result):
34+
// When there are multiple key names known statically
35+
const sanitizedOpts = (opts) => {
36+
const result = {
37+
...PRIVATE_OPTS,
38+
..opts,
39+
};
2440

25-
```js
26-
const removeB1 = (src) => ({
27-
...src,
28-
b: Object.keys(src.b).reduce((acc, key) => key === 'b1' ? acc : { ...acc, [key]: src.b[key] }, {}),
29-
});
41+
// Removing the key "keyThatMustNotBeThere" from the result
42+
delete result.keyThatMustNotBeThere;
43+
delete result.keyThatAlsoMustNotBeThere;
44+
45+
return result;
46+
};
47+
48+
// When the key name is not known beforehand
49+
const sanitizedOpts = (opts) => {
50+
const result = {
51+
...PRIVATE_OPTS,
52+
..opts,
53+
};
54+
55+
// Removing the key stored in KEY_THAT_MUST_NOT_BE_THERE from the result
56+
delete result[KEY_THAT_MUST_NOT_BE_THERE];
57+
58+
return result;
59+
};
60+
61+
// When there are multiple keys to remove
62+
const sanitizedOpts = (opts) => {
63+
const result = {
64+
...PRIVATE_OPTS,
65+
..opts,
66+
};
67+
68+
// Removing all the key names stored in KEYS_TO_REMOVE from the result
69+
KEYS_TO_REMOVE.forEach((key) => {
70+
delete result[key];
71+
});
72+
73+
return result;
74+
};
3075
```
3176

32-
This line, in particular, requires some time to understand what's going on:
77+
Removing keys this way has a few significant disadvantages:
78+
- It is wordy.
79+
- It is non-declarative and breaks the declarative paradigm of object spread.
80+
- It makes the JS engine do extra work. First, the object spread will copy all the properties from all objects, and then we have to manually remove some of them, consuming additional CPU cycles, allocating the memory, and potentially, making the garbage collector care about a few more objects.
81+
82+
What if there was a way to give developers more declarative superpowers here?
3383

34-
```js
35-
b: Object.keys(src.b).reduce((acc, key) => key === 'b1' ? acc : { ...acc, [key]: src.b[key] }, {}),
36-
````
3784

85+
## Proposed solution
3886

39-
## Proposal: the minus operator
87+
### The key exclusion syntax
4088

41-
What I propose is to improve readability of that quite typical operation, by introducing a special operator, expressed by a minus sign `-`, into the language standard. This operator would allow omitting a key by prependng it with this operator, in the object spread body:
89+
So, what if we could tell the JS engine not to copy some of the keys to the spread result at all? I am glad to present to you the key exclusion syntax, also mentioned as the minus syntax below.
90+
91+
Looking into the aforementioned examples, all the problems would be solved elegantly:
4292

4393
```js
44-
const removeB1 = (src) => ({
45-
...src,
46-
b: {
47-
...src.b,
48-
-b1, // A minus operator!
49-
},
50-
});
94+
// When the key name is known statically
95+
const sanitizedOpts = (opts) => {
96+
return {
97+
...PRIVATE_OPTS,
98+
..opts,
99+
-keyThatMustNotBeThere, // The key exclusion syntax in action!
100+
};
101+
};
102+
103+
// When there are multiple keys with known names
104+
const sanitizedOpts = (opts) => {
105+
return {
106+
...PRIVATE_OPTS,
107+
..opts,
108+
-keyThatMustNotBeThere,
109+
-keyThatAlsoMustNotBeThere, // ...supporting multiple keys!
110+
};
111+
};
112+
113+
// When the key name is not known beforehand
114+
const sanitizedOpts = (opts) => {
115+
return {
116+
...PRIVATE_OPTS,
117+
..opts,
118+
-[KEY_THAT_MUST_NOT_BE_THERE], // ... and dynamic keys!
119+
};
120+
};
121+
122+
// When there are multiple keys to remove
123+
const sanitizedOpts = (opts) => {
124+
return {
125+
...PRIVATE_OPTS,
126+
..opts,
127+
-[...KEYS_TO_REMOVE], // ... and multiple dynamic keys!
128+
};
129+
};
51130
```
52131

53-
I propose using minus (`-`) sign because it doesn't conflict with existing language semantics, and thus is easier to implement.
132+
#### Why the dash/the minus character?
133+
134+
The assumption that it would be easier for the JS engines and transpilers to implement (because currently, the dash character is not expected before the key names in the object spread and thus won't conflict with any existing valid syntax).
54135

55136

56137
### Execution order

0 commit comments

Comments
 (0)