Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does object spread work if it is not an iterable?

I'm learning about new uses of spreading. I realize that object spread is an ES2018 proposal. It works in Node 10.5 in the following manner:

const oldObj = {name:"doug", age:34};
const newObj = {...oldObj}; 
console.log(newObj); // { name: 'doug', age: 34 }

One interesting use of spreading is to convert iterables into arrays. It works fine with Maps, for example, giving you an array of arrays of value pairs

const mappie = new Map().set("name", "doug").set("age", 234).set("profession", "seeker of Cthulhu");

const arr1 = [...mappie];

console.log(arr1); // [ [ 'name', 'doug' ], [ 'age', 234 ], [ 'profession', 'seeker of Cthulhu' ] ]

But I can't use this on an object

const obj = {
  name: "doug",
  age: 234,
  profession: "seeker of Chthulhu"
};

const arr2 = [...obj];
console.log(arr2);

gives me

TypeError: obj is not iterable

OK, I understand that object is not iterable (at the moment). But is object spreading some kind of different creature than iterable spreading? That is it will work in some circumstances but not others, and one should just realize that objects can spread into objects only, and you can't spread them into arrays? Or am I missing the big picture? I'm trying to understand the new uses of spread, and any enlightenment appreciated....

like image 934
Cerulean Avatar asked Jun 28 '18 13:06

Cerulean


2 Answers

But is object spreading some kind of different creature than iterable spreading?

Yes. Property spread doesn't use iteration at all. It's new primary syntax, the runtime semantics of which are defined by the spec, and not in terms of iterables/iteration:

PropertyDefinition:...AssignmentExpression

  1. Let exprValue be the result of evaluating AssignmentExpression.
  2. Let fromValue be ? GetValue(exprValue).
  3. Let excludedNames be a new empty List.
  4. Return ? CopyDataProperties(object, fromValue, excludedNames).

Property spread is specifically for object properties, there's no additional generalization of it like there is with iterable spread. (Nor it is immediately obvious how there would be. :-) )

For your const arr2 = [...obj]; use-case, you'd probably want Object.entries:

const arr2 = Object.entries(obj);

Example:

const obj = {
  name: "doug",
  age: 234,
  profession: "seeker of Chthulhu"
};
const arr2 = Object.entries(obj);
console.log(arr2);

...or Object.keys if you just want property names, or Object.values if you just want values.

Of course, you can make an object iterable if you like: Just give it an iterator. For instance:

const obj = {
  name: "doug",
  age: 234,
  profession: "seeker of Chthulhu",
  * [Symbol.iterator]() {
    return yield* Object.entries(this);
  }
};
const arr2 = [...obj];
console.log(arr2);

And you can make instances of any classes you create iterable by defining an appropriate iterator for them and providing a Symbol.iterator-named property on the prototype:

class ExampleList {
    constructor() {
        this.head = this.tail = null;
    }

    add(value) {
        const entry = {value, next: null};
        if (!this.tail) {
            this.head = this.tail = entry;
        } else {
            this.tail = this.tail.next = entry;
        }
    }

    * [Symbol.iterator]() {
        for (let current = this.head; current; current = current.next) {
            yield current.value;
        }
    }
}

const list = new ExampleList();
list.add("a");
list.add("b");
list.add("c");

for (const value of list) {
    console.log(value);
}

That is it will work in some circumstances but not others...

Well, that's true of spread notation in general. Property spread is only defined within object initializers, and only works when the operand is some kind of object. (And its counterpart, the new property rest notation, is defined within destructuring assignment patterns.) Iterable spread is only defined in array initializers and function argument lists, and only works when its operand is some kind of iterable. (And its counterpart, iterable rest notation, is defined within destructuring assignment patterns that create arrays.)

like image 80
T.J. Crowder Avatar answered Sep 28 '22 01:09

T.J. Crowder


T.J. Crowder's answer is correct but this is too long for a comment and hopefully gives you a better appreciation for the utility: consider the frequent case that you want a couple of properties from an object and put them in a new object. Several third party libraries like Ramda and lodash implement utility functions that do this, but with a combination of shorthand properties, destructuring, and object spread it can be done succinctly in vanilla JS:

const foo = { a: 1, b: 2, c: 3, d: 4 };
const { a, d } = foo;
const bar = { a, d };
console.log(bar); // { a: 1, d: 4 }

If you don't mind abusing the comma operator you can shorten this even further:

let a, d, bar, foo = { a: 1, b: 2, c: 3, d: 4 };
bar = ({a, d} = foo, {a, d});
like image 32
Jared Smith Avatar answered Sep 27 '22 23:09

Jared Smith