Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Confirm the answer according to #5 #6

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 79 additions & 37 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ This proposal is currently [stage 1](https://github.com/tc39/proposals/blob/mast
Let us consider a few scenarios from the real world to understand what we are trying to solve in this proposal.

* On `MouseEvent` we are interested on `'ctrlKey', 'shiftKey', 'altKey', 'metaKey'` events only.
* We have a `configObject` and we need `['dependencies', 'devDependencies', 'peerDependencies']` from it.
* We have a `configObject` and we need `['dependencies', 'devDependencies', 'peerDependencies']` from it.
* We have an `optionsBag`and we would allow on `['shell', 'env', 'extendEnv', 'uid', 'gid']` on it.
* From a `req.body` we want to extract `['name', 'company', 'email', 'password']`
* Checking if a component `shouldReload` by extracting `compareKeys` from `props` and compare it with `prevProps`.
Expand Down Expand Up @@ -77,7 +77,7 @@ Object.omit(obj[, omittedKeys | predictedFunction(currentValue[, key[, object]])
- `obj`: which object you want to pick or omit.
- `pickedKeys` (**optional**): keys of properties you want to pick from the object. The default value is an empty array.
- `omittedKeys` (**optional**): keys of properties you want to pick from the object. The default value is an empty array.
- `predictedFunction` (**optional**): the function to predicted whether the property should be picked or omitted. The default value is an identity: `x => x`.
- `predictedFunction` (**optional**): the function to predict whether the property should be picked or omitted. The default value is an identity: `x => x`.
- `currentValue`: the current value processed in the object.
- `key`: the key of the `currentValue` in the object.
- `object`: the object `pick` was called upon.
Expand All @@ -104,7 +104,7 @@ Object.omit({a : 1, b : 2}, ['b']); // => {a: 1}
Object.pick({a : 1, b : 2}, ['c']); // => {}
Object.omit({a : 1, b : 2}, ['c']); // => {a: 1, b: 2}

Object.pick([], [Symbol.iterator]); // => {Symbol(Symbol.iterator): f}
Object.pick([], [Symbol.iterator]); // => {Symbol(Symbol.iterator): Array.prototype[Symbol.iterator]}
Object.pick([], ['length']); // => {length: 0}

Object.pick({a : 1, b : 2}, v => v === 1); // => {a: 1}
Expand All @@ -124,54 +124,67 @@ Object.pick({a : 1, b : 2}, (v, k) => k !== 'b'); // => {a: 1}
- square brackets:

```js
({a : 1, b : 2, c : 3}).['a', 'b']; // => {a : 1, b : 2}
({a : 1, b : 2, c : 3}).['a', 'b']; // => {a: 1, b: 2}

const keys = ['a', 'b'];
({a : 1, b : 2, c : 3}).[keys[0], keys[1]]; // => {a : 1, b : 2}
({a : 1, b : 2, c : 3}).[...keys]; // => {a : 1, b : 2}
({a : 1, b : 2, c : 3}).[keys[0], keys[1]]; // => {a: 1, b: 2}
({a : 1, b : 2, c : 3}).[...keys]; // => {a: 1, b: 2}
```

- curly brackets

```js
({a : 1, b : 2, c : 3}).{a, b} // => {a : 1, b : 2}
({a : 1, b : 2, c : 3}).{a, b} // => {a: 1, b: 2}

const keys = ['a', 'b'];
({a : 1, b : 2, c : 3}).{[keys[0]], b}; // => {a : 1}
({a : 1, b : 2, c : 3}).{[...keys]}; // => {a : 1, b : 2}
({a : 1, b : 2, c : 3}).{[keys[0]], b}; // => {a: 1}
({a : 1, b : 2, c : 3}).{[...keys]}; // => {a: 1, b: 2}

// Similar to destructuring
({a : 1, b : 2, c : 3}).{a, b : B}; // => {a : 1, B : 2}
({a : 1, b : 2, c : 3}).{a, b : B}; // => {a: 1, B: 2}
```

Currently, there is a disagreement on whether properties with default assignment values should be picked.
~~Currently, there is a disagreement on whether properties with default assignment values should be picked.~~ It has been reviewed in [#6](https://github.com/tc39-transfer/proposal-object-pick-or-omit/pull/6) which confirmed that the `.{...}` statement should denote what the new object should contain like restructuring:

```js
// If considering the meaning of picking, the initial value has no meanings
({a : 1, b : 2, c : 3}).{a, d = 2}; // => {a : 1}

// If considering as "restructuring", the shortcut has its reason to pick
({a : 1, b : 2, c : 3}).{a, d = 2}; // => {a : 1, d : 2}
({a : 1, b : 2, c : 3}).{a, d = 2}; // => {a: 1, d: 2}
({a : 1, b : 2, c : 3, d : 4}).{a, d = 2}; // => {a: 1, d: 4}
```

Nevertheless, it is just a simple vision, and feel free to discuss.
Nevertheless, it is just a simple vision, and feel free to discuss it.

### FAQ

1. When it comes to the prototype chain of an object, should the method pick or omit it? (The answer may change)
1. When it comes to the prototype chain of an object, should the method pick or omit it?

A: The implementation of [`_.pick`](https://lodash.com/docs/4.17.15#pick) and [`_.omit`](https://lodash.com/docs/4.17.15#omit) by Lodash has taken care about the chain. To keep the rule, we can pick off properties of prototype, but can't omit them:
A: Consistent with destructuring: we can explicitly pick off properties of the prototype, but we can't modify them by calling `Object.omit`.

```js
Object.pick({a : 1}, ['toString']); // => {toString: f}
Object.omit({a : 1}, ['toString']).toString; // => ƒ toString() { [native code] }
Object.pick([1, 2, 3], ['length']); // => {length: 3}
// equivalent to the behavior
const {length} = [1, 2, 3];
length; // => 3

// cannot omit the prototype of an array by calling `Object.omit`
const arr = [1, 2, 3];
Object.omit(arr, ['length']);
arr.length; // => 3
```

The same rule applies to `__proto__` event if it has been [deprecated](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/proto), because the proposal should be pure enough to not specify a special logic to eliminate deprecated properties:
The implementation of [`_.pick`](https://lodash.com/docs/4.17.15#pick) and [`_.omit`](https://lodash.com/docs/4.17.15#omit) by Lodash has also taken care about the chain.

The same rule applies to `__proto__` event if it has been [deprecated](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/proto) because the proposal should be pure enough to not specify a special logic to eliminate deprecated properties:

```js
Object.pick({}, ['__proto__']); // => {__proto__: {...}}
Object.omit({}, ['__proto__']).__proto__; // => {...}
Object.pick({}, ['__proto__']); // => {__proto__: Object.prototype}
// equivalent to the behavior
const {__proto__} = {};
__proto__; // => Object.prototype

Object.omit({}, ['__proto__']).__proto__; // => Object.prototype
// equivalent to the behavior
const {__proto__, ...res} = {}
res.__proto__; // => Object.prototype
```

In some opinions, picking off or omitting properties from the prototype chain should make the method more extendable:
Expand All @@ -181,41 +194,70 @@ Object.pick({a : 1, b : 2}, (v, k) => k !== 'b'); // => {a: 1}
const omitOwn = (obj, keys) => Object.omit(obj, keys.filter(key => obj.hasOwnProperty(key)));
```

2. What is the type of the returned value?
2. What is the type of returned value?

A: All these methods should return plain objects:
A: Consistent with destructuring: should return plain objects.

```js
Object.pick([]); // => {}
Object.pick([]); // => {}
Object.omit([]); // => {}
Object.pick(new Map()); // => {}
Object.pick(new Map()); // => {}
Object.omit(new Map()); // => {}

// equivalent to the behavior
const {...res} = [];
res instanceof Array; // => false
const {...res} = new Map();
res instanceof Map; // => false
const {...res} = new Set();
res instanceof Set; // => false
```

3. How to handle `Symbol`?

A: `Symbol` should just be considered as properties within `Symbol` keys, and they should obey the rules mentioned above:
A: Consistent with destructuring.

```js
Object.pick([], [Symbol.iterator]); // => {Symbol(Symbol.iterator): f}, pick off from the prototype
Object.pick([], [Symbol.iterator]); // => {Symbol(Symbol.iterator): Array.prototype[Symbol.iterator]}, pick off from the prototype
// equivalent to the behavior
const {[Symbol.iterator] : iter} = [];
iter; // => Array.prototype[Symbol.iterator]

Object.omit([], [Symbol.iterator]); // => {}, plain objects
// equivalent to the behavior
const {[Symbol.iterator] : iter, ...res} = [];
res instanceof Array; // => false

const symbol = Symbol('key');
Object.omit({a : 1, [symbol]: 2}, [symbol]); // => {a : 1}
Object.omit({a : 1, [symbol]: 2}, [symbol]); // => {a: 1}
// equivalent to the behavior
const {[symbol] : _, ...res} = {a : 1, [symbol]: 2};
res; // => {a : 1}

Object.prototype[symbol] = 'test'; // override prototype
Object.pick({}, [symbol]); // => {Symbol(key): "test"}, pick off from the prototype
Object.omit({}, [symbol])[symbol]; // => "test", cannot omit properties from the prototype
// equivalent to the behavior
const {[symbol] : sym, ...res} = {};
sym; // => 'test'
ljharb marked this conversation as resolved.
Show resolved Hide resolved
res[symbol]; // => 'test'
```

4. If some properties of an object are not accessible like throwing an error, can `Object.pick` or `Object.omit` operate such an object?

A: I suggest throwing the error wrapped by `Object.pick` or `Object.omit`, but it is **NOT the final choice**:
A: Consistent with destructuring: throw the error wrapped by `Object.pick` or `Object.omit`.

```js
Object.pick(Object.defineProperty({}, 'key', {
get() { throw new Error() }
get() { throw new Error(); }
}), ['key']);
// equivalent to the behavior
const o = Object.defineProperty({}, 'key', {
get() { throw new Error('custom'); }
});
try { const {key} = o; } catch (e) {
e.message // => 'custom'
}
```

The error stack will look like this:
Expand All @@ -224,7 +266,7 @@ Object.pick({a : 1, b : 2}, (v, k) => k !== 'b'); // => {a: 1}
Uncaught Error
at Object.get (<anonymous>:2:20)
at Object.pick (<anonymous>:2:10)
at <anonymous>:1:8
at <anonymous>:1:8
```

5. In comparison with [**proposal-shorthand-improvements**](https://github.com/rbuckton/proposal-shorthand-improvements), when should we use these two methods?
Expand All @@ -233,16 +275,16 @@ Object.pick({a : 1, b : 2}, (v, k) => k !== 'b'); // => {a: 1}

```js
postData({[key1] : o[key1], [key2] : o[key2]});
postData(Object.pick(o, [key1, key2]));
postData(Object.pick(o, [key1, key2]));
```

6. Why can't be defined on the `Object.prototype` directly?

A: As `Object` is especially fundamental, and both of them will result in conflicts of properties of any other objects. In shorthand, if defined, any objects inherited from `Object` with `pick` or `omit` defined in its prototype should break.
A: As `Object` is especially fundamental, both of them will result in conflicts of properties of any other objects. In shorthand, if defined, any objects inherited from `Object` with `pick` or `omit` defined in its prototype would break. Also, objects that inherit from `null` would be left unable to use this functionality.

7. Why not define filtered methods corresponding to two actions: [`pickBy`](https://lodash.com/docs/4.17.15#pickBy) and [`omitBy`](https://lodash.com/docs/4.17.15#omitBy) like Lodash?

A: It is [unnecessary](https://github.com/aleen42/proposal-object-pick-or-omit/issues/2) to double two methods, because it can be combined into the argument instead:
A: It is [unnecessary](https://github.com/aleen42/proposal-object-pick-or-omit/issues/2) to double two methods because they can be combined into the argument instead:

Besides, the passing filtered method can be easily reversed with equal meaning, and it means that `omitBy` can be easily defined as `pickBy`'s inverse.

Expand Down