Code Monkey home page Code Monkey logo

proposal-object-rest-spread's Introduction

Object Rest/Spread Properties for ECMAScript

ECMAScript 6 introduces rest elements for array destructuring assignment and spread elements for array literals.

This proposal introduces analogous rest properties for object destructuring assignment and spread properties for object literals.

Specification

Rest properties collect the remaining own enumerable property keys that are not already picked off by the destructuring pattern. Those keys and their values are copied onto a new object.

let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
x; // 1
y; // 2
z; // { a: 3, b: 4 }

Spread properties in object initializers copies own enumerable properties from a provided object onto the newly created object.

let n = { x, y, ...z };
n; // { x: 1, y: 2, a: 3, b: 4 }

Transpilers

Babel

Bublé

JSTransform

TypeScript

It is a Stage 4 proposal for ECMAScript.

This proposal only iterates over own properties. See why this matters.

proposal-object-rest-spread's People

Contributors

adrianheine avatar chicoxyzzy avatar gaillota avatar gsathya avatar keithamus avatar littledan avatar ljharb avatar maxdow avatar mhujer avatar mmxw avatar not-an-aardvark avatar pitaj avatar sandersn avatar sebmarkbage avatar sophiebits avatar styfle avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

proposal-object-rest-spread's Issues

Work against protocol rather than concrete type

Hello, hope you don't mind questions/suggestions here - wasn't sure where else to note them.

The existing spread operator works off the Iterator protocol, which makes it available to custom types.

The proposed spread operator here appears to work only on objects and their own enumerable properties, which makes it difficult for custom types to play nicely.

If the idea is that this always desugars to Object.assign, then I guess such a change would be outside the scope of this bit of spec?

Rationale for ignoring newly defined setters

I noticed the following caveat in Issues.md:

Setters

Properties on object literals are defined as new value on the object. It does not invoke any newly defined setters. This is different than what Object.assign on a recently defined object does.

let record = { x: 1 };
let obj = { set x() { throw new Error(); }, ...record }; // not error
obj.x; // 1

What is the rationale for this? It feels a bit surprising, as it breaks with the mental model of being sugar over Object.assign.

Should Maps be spreaded?

like Sets can be spread in an array, shouldn't:

var m=new Map([[1,2],[3,4]]); JSON.stringify({...m})

work as well?

Spread operator and proxies

I have the following use case:

let st = {...};  // st is immutable

let stProxy = new Proxy(st, ...);

Now, I would like to use spread syntax to construct a new immutable version of stProxy:

let stProxy2 = {...stProxy, var: value};

However, the stProxy functionality would be lost then.

So what if {...st, ...} desugars to something like that instead:

Object.assign(st.clone ? st.clone() : {}, st, ...);

Support destructuring defaults

It's only logical that object rest/spread enjoys destructuring defaults as well:

function f ({head, ...others = {}} = {}) {
  console.log(head, others);
}

f({head: 'is head'});

Demo

Ignoring properties

There's a little bit of discussion in issue #16 about filtering or ignoring properties.

let x = {a: 1, b: 2, c: 3, md5: '83b0f2d70d7546ee634f801264c4a9b3'};
let { md5, ...y } = x;

The problem with this is the introduction of the unwanted variable md5. This can be avoided by using:

let { md5: {}, ...y } = x;

This approach also works with computed property names:

let x = {a: 1, b: 2, c: 3, md5: '83b0f2d70d7546ee634f801264c4a9b3'};
let omit = 'md5';
let { [omit]: {}, ...y } = x;

However, this code is at risk of a Cannot destructure undefined error if the property value is null or undefined.

We need another way.

Array destructing allows this:

let x = [1, 2, 3]
let [ , ...y ] = x; // → y = [2, 3]

Perhaps this would the closest comparable syntax:

let x = {a: 1, b: 2, c: 3, md5: '83b0f2d70d7546ee634f801264c4a9b3'};
let { md5:, ...y } = x;

let omit = 'md5';
let { [omit]:, ...z } = x;

Does this proposal require anything other than making the AssignmentElement optional in the AssignmentProperty production?

Using object rest properties to immutably delete multiple keys in runtime

We know we can get the rest entries of an object:

let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
x; // 1
y; // 2
z; // { a: 3, b: 4 }

But if the x, y is in an array, for example [ 'x', 'y' ], generated in runtime, I don't know those keys when coding, maybe we need this kind of syntax:

const keys = [ 'x', 'y' ];
// syntax error here, some new syntax is needed
let { ...keys, ...z } = { x: 1, y: 2, a: 3, b: 4 };
x; // 1
y; // 2
z; // { a: 3, b: 4 }

Spreading class properties

This might be a slightly out of scope, but does it not make sense to allow spreading inside a class definition? I expected something like the code below to work, and was a bit surprised at first sight that it did not:

const defaults = {
    value: 0
}

class Example {
    ...defaults
}

My expectation was that the properties would be spread on the class prototype.

Another issue, worth mentioning

For Issues.md

With Object.assign it is possible to create a new Object but also the reuse an old one.

const object3 = Object.assign({}, object1, object2); // same as spread, always works

const object3 = Object.assign(object1, object2); // reuses object1, potentially faster

With spread operator there is no way to opt in to that potential performance improvement.

Move to tc39 organization

After a proposal reaches Stage 1, it is supposed to be in transferred to TC39, for ECMA archiving. You can do this by transferring ownership to @bterlson and then he puts it in tc39. Unfortunately, the process breaks links to the draft spec...

Filter keys in spread object

The issue #3
had an example with lodash/omit. While complete computed properties were determined to be undesirable, perhaps the specific use case of filtering keys would be helpful.

let x = {a: 1, b: 2, c: 3, md5: '83b0f2d70d7546ee634f801264c4a9b3'};
let y = {...x, !md5};
console.log(y); // {a: 1, b: 2, c: 3}

This would allow us to golf blocks like

let y = {...x};
delete y.md5;
return y;

to

return {...x, !md5};

stage-4. what remains from the list of "Entrance Criteria" list?

process document declares, that proposal will become stage-4, when following criteria are met:

  • Test262 acceptance tests have been written for mainline usage scenarios, and merged
  • Two compatible implementations which pass the acceptance tests
  • Significant in-the-field experience with shipping implementations, such as that provided by two independent VMs
  • A pull request has been sent to tc39/ecma262 with the integrated spec text
  • The ECMAScript editor has signed off on the pull request

so my question is what remains from this list? compat-table says implementation exists in Chrome (hidden by flag) and in babel. I assume its not enough, right? Also, cant find anything related about vendors' plans on this feature on https://platform-status.mozilla.org/

@sebmarkbage, can you help to understand whats left to be done to get stage-4 for this proposal?

Deleting keys in object spread

Has there been any discussion about removing/filtering keys during spread?

For example:

let x = { a: 1, b: 2, c: 3, };
let y = { ...x, c: undefined, };
// y is { a: 1, b: 2, c: undefined, }
let z = { ...x, delete c, }; // possible syntax
// z is { a: 1, b: 2, }

At the moment, z is currently achievable using this multi-statement code:

let z = { ...x, };
delete z.c;

or using this single-expression but rather verbose and potentially-less-optimizable code:

let z = Object.entries(x).reduce((o, [k, v]) => (k !== 'c' && (o[k] = v), o), {})

It'd be nice if it could be done in an object spread expression, perhaps using my proposed syntax.

{ let, cont}

I would like to see this in relise:
{ let x, const y, z } = functionReturnObject(w);

Use object iterator, if present

If you use ...o in a spread context and the o in question has defined a Symbol.iterator, it would make more sense to use that than to just iterate own properties, in the same way that arrays work.

So, it should first check for the Symbol.iterator, and use it if present, otherwise fall back to the own-keys iteration.

Inherited enumerated properties

Hey,

I've been watching this proposal for a while now fearing that it would ignore the Liskov Substitution Principle, cause silly bugs in code that one would otherwise presume to behave as their ES5 counterpart and not align with the intent of ES6 non-enumerated class members. I'll go over the arguments in detail, but they all ask why the implementation of object spread is in own-properties terms — Object.assign — and propose that's not a good choice.

Liskov Substitution Principle

LSP, I'd argue as one fundamental pillar of object orientation, states that any object must be replaceable by its subtype. In a prototypical language such as JavaScript, an object inheriting from another is a subtype of the other. In plain JavaScript, that holds pretty much in all cases — it surely holds for regular property access and it'd be silly if it didn't. That is, the following two name variables are equivalent.

var a = {name: "John"}
var b = Object.create(a); b.age = 42
var nameA = a.name
var nameB = b.name

However, if we interject object rest spread there, let's say in some intermediate function, we'll break LSP and introduce a hard to spot bug for no benefit:

function printName(obj) { console.log(obj.name) }

function printAgeAndName(obj) {
	var {age, ...rest} = obj
	console.log(obj.name)
	printName(rest)
}

var person = {name: "John"}
var agedPerson = Object.create(person); agedPerson.age = 42
printAgeAndName(agedPerson)

Mind you, printAgeAndName is doing the right thing with its intent of passing a supertype to printName, that is, a type without the age property (regardless of where age sits on the prototype hierarchy). It is NOT doing the right thing, however, by losing name entirely. Perhaps previously, without rest properties, printAgeAndName didn't care about removing age before passing it on as-is, and that worked, too.

But now what could've been an innocent refactoring, turned out to be API breakage dependent on the implementation of ...rest. I wouldn't expect the regular Joe to realize that. It's perfectly reasonable to imagine ...rest being a shorthand for typing all the other properties out, and if you'd done that, you'd have gotten their proper, inherited values. After all, you already know the "type" or structure of the record you're dealing with when you use the ...rest syntax. It's not a map of random keys and values.

ES6 non-enumerated class members

At some point ES6 class properties were made non-enumerable. Presumably to allow their data properties to be enumerated, skipping methods. That allows for record-like classes to easily written and used like plain objects, enabling lazy computation:

class Url {
	constructor(href) { this.href = href }
	toString() { return this.href }
}

Object.defineProperty(Url.prototype, "path", {
	value: function() { return this.href.split("?", 1)[0] },
	configurable: true, writable: true, enumerable: true
})

var url = new Url("foo/bar?baz")
for (var key in url) console.log(key)

The for-in enumeration will list both href and path, but compute path lazily when accessed (memoization left as an exercise to the reader). Now imagine a situation similar to the printAgeAndName I brought up above. A function that handled this memoizing class just fine runs it through ...rest. Or perhaps to just make a copy for mutating. We'll have, again, lost enumerable properties that were implemented on the prototype for efficiency.

Summary

I'd argue the only valid criteria for inclusion of a properties should be its enumerability.

  • It would be consistent by adhering to the Liskov Subtitution Principle.
  • It would be aligned with the grain of a prototypical language.
  • It would be aligned with property access
  • It would allow objects or record with behavior (the definition of object orientation in the first place) for lazy computation as displayed in the Url example above.
  • Have other good design qualities that I can't be bothered to go in to now. 😇

Spread operator copies Symbol property from one to another object

Current implementation of the spread operator in browsers copy Symbol property when used.

What I see here is inconsistency.
On the one hand spread operator and Object.assign both do copy Symbol property.
On the other hand, Symbol property doesn't appear in for...in loop and completely ignored by JSON.stringify and JSON.parse.

Steps to reproduce the problem:

  1. Define object on which Symbol will be used as property name (e.g. obj[Symbol("secret property")] = "secret value")
  2. Using spread operator copy all properties to another object (e.g. const newObj = {...obj})

Spread operator should copy only own enumerable (which is not relevant to the property named using Symbol as it's not enumerable) properties from a provided object onto a new object.

Checked in:
Chrome version: 63.0.3239.132
(Windows 7)

working example: https://goo.gl/SkHd3H

Couldn't "excludedNames" be a Set instead of a List?

I don't know if I'm missing some context here, however using excludedNames as a Set will improve the http://sebmarkbage.github.io/ecmascript-rest-spread/#AbstractOperations-CopyDataProperties asymptotic time complexity to O(from.[OwnPropertyKeys].length + excludedNames.length) while using a List it is O(from.[OwnPropertyKeys].length * excludedNames.length).

If I'm not misreading the Spec, we are using excludedNames just in CopyDataProperties set 4.a and have an element just once in this data structure doesn't change it's correctness.

Inconsistent behaviour

Let's check such example:

const a = { b: 1 };
const getC = e => {
	e.b++;
  	return e.b;
}

const b = { ...a, c: getC(a) };
console.log(b);

Property a.b is being changed by reference.
When I run it in browser (Chrome 61), I get next result: { b: 1, c: 2 } (as expected).

And while using object-rest-spread plugin, it just wraps this into _extend function like

const b = _extends({}, a, { c: getC(a) });

Which first calculate result of function getC and then transfer management to _extends function. This leads to unexpected result. ({ b: 2, c: 2 })

Maybe there is ability to add some kind of lazy evaluation of functions, which are in arguments.

Using spread in computed property names

Just came across this idea and it can be useful. Any thoughts?

let arr = ['foo1', 'foo2', 'foo3'];
let sharedData = "sameValue";

let payload = {
  [..arr]: sharedData
}

Computed destructuring

First of all thanks for this great proposal. I use it in production with Babel and it’s great.

I wondered if you considered “computed destructuring”, e.g.

let { [...properties], ...rest } = {}; 

I like that this keeps the symmetry of the assignment statement:

{ property: name, [ computed destructuring ], ...rest } = { property: value, [ computed property ], ...spread }

This would make implementing functions like lodash’s omit and pick trivial:

function omit(object, omitted) {
  let { [...omitted], ...rest } = object;
  return rest;
}

omit({ x: 1, y: 2, z: 3 }, ['x', 'z']) // { y: 2 }

function pick(object, picked) {
  let { [...picked], ...omitted } = object;
  let { [...Object.keys(omitted)], ...rest } = object;
  return rest;
}

pick({ x: 1, y: 2, z: 3 }, ['x', 'z']) // { x: 1, z: 3 }

It also opens up interesting possibilities, like declaring all exported properties of a common.js module in the module’s scope:

const { [...Object.keys(require('lodash'))] } = require('lodash');

typeof omit === 'function' // true

Maybe it’s possible to add “computed destructuring” to ES2015’s module syntax as well.

This came to my mind yesterday and I just wanted to ask if you have thought about this syntax already or if that’s even possible.

Object rest inside function argument destructuring

I noticed that the transform-object-rest-spread babel plugin doesn't support the following syntax:

({ someArg = true, ...otherOpts } = {}) => {}

That is, it fails to recognise the ...otherOpts object rest destructuring. I was wondering if this was just a limitation of the plugin or if it wasn't supported by this spec.

Does conditional spreads are part of this proposal?

getters/setters

My perspective on this proposal has always been that it's sugar for Object.assign with a new object as the first argument.

However, it's been brought to my attention that this does not hold in the following case:

const a = { a: 1 };
const b = 2;

{ ...a, b, get c() { return 3; } }
// vs
Object.assign({}, a, { b, get c() { return 3; } })

In the latter case, you'll end up with { a: 1, b: 2, c: 3 }. In the former, however, you'll end up with { a: 1, b: 2, get c() { return 3; } }.

This same question comes up wrt setters, and [[HomeObject]], but I think this simpler example is sufficient to discuss the disparity.

Is this difference intentional? Is it desired?

My expectation would be that any object with spread syntax inside it can be trivially replaced with Object.assign, and vice versa.

(related: babel/babel#5095)

Is the behavior of {...false} defined? (Conditionally setting key)

This stackoverflow answer recommends using spread operator to conditionally set a key in a object.

const animal = {species: 'cat'};
const vocalAnimal = {
    ...animal,
    ...animal.species === 'cat' && {purr: 'meow'},
    ...animal.species === 'dog' && {bark: 'woof'},
} 
// becomes: { "species": "cat", "purr": "meow" }

But this depends on a behavior of ...false being ignored, like ...null or ...undefined.
Similar code without depending on the behavior might be:

const animal = {species: 'cat'};
let vocalAnimal = {...animal};
if(animal.species === 'cat'){
    vocalAnimal.purr = 'meow';
}
if(animal.species === 'dog'){
    vocalAnimal.bark = 'woof';
}

So is the behavior well defined/ intended/ dependable?

Object rest spread does not consider subsequent variables

Should the following use of rest spread be valid? It is currently supported by babel-plugin-transform-object-rest-spread.

const { ...rest, b } = { a: 'a', b: 'b' };
console.log('rest:', rest);
console.log('b:', b);

/* output:
rest: {"a":"a","b":"b"}
b: b
*/

@ljharb suggested that this should throw on compilation.

spread with null

I know spread is used for array liked data.
But I found that null also support spread.
Such as:
let result = {...null}
is workd.

But, null is not array liked data.

Typo in Spread.md – "throws"

The specification has a typo – instead of the correct keyword throw, a couple of the code examples contain the word throws.

Spreading object into array does nothing

This is what happens:

let x = { a: 1, b: 2 }
let y = [ ...x ]
y // []

What should happen?

Someone on my team suggested [{ a: 1 }, { b: 2 }] which I think is pretty cool, but I would also be in favor of a type error: Cannot spread object inside array or something.

destruct objects with alias

const { name, Tags as tags } = this.state;

avoids using:

const values = {
    name,
    tags: Tags,
};

or

const tag = Tag;
const values = {
    name,
    tags,
};

can i parse string to number while destructuring ?

This is just a question.

For example, I have an input like this:

let input = {latitude: "17.0009", longitude: "82.2108"}

Where latitude and longitude key's value are strings, but I want to parse them into a number while destructuring.

let input = {latitude: "17.0009", longitude: "82.2108"}
let {latitude,longitude} = input

console.log(typeof latitude,typeof longitude)
// string string

I can see in babel repl that this takes a reference of an object and then access each key. So the above code is the same as:

"use strict";

 var arr = {
   latitude: "17.0009",
   longitude: "82.2108"
  };
 var latitude = arr.latitude,
     longitude = arr.longitude;

I want do something like using the destructuring syntax itself.

"use strict";

var arr = {
  latitude: "17.0009",
  longitude: "82.2108"
};
var latitude = Number(arr.latitude),
    longitude = Number(arr.longitude);

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.