diff --git a/README.md b/README.md index 426eb87..fa9486c 100644 --- a/README.md +++ b/README.md @@ -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`. @@ -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. @@ -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} @@ -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: @@ -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' + 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: @@ -224,7 +266,7 @@ Object.pick({a : 1, b : 2}, (v, k) => k !== 'b'); // => {a: 1} Uncaught Error at Object.get (:2:20) at Object.pick (:2:10) - at :1:8 + at :1:8 ``` 5. In comparison with [**proposal-shorthand-improvements**](https://github.com/rbuckton/proposal-shorthand-improvements), when should we use these two methods? @@ -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.