2

Given the new 'Symbol' primitive in ES6, is it possible to modify/set the behavior of a for...of loop in an object?

I'm creating a little utility for 'deep extracting' values from an 'iterable' (which I define for my purposes as an Object, Array, Map or Set).

Array, Map and Set make use of for..of loops, however plain objects do not. For consistency, I'd like objects to make use of this loop (and should iterate over values rather than properties (as for...in allows you to do)).

Can this be done?

sookie
  • 2,437
  • 3
  • 29
  • 48
  • 1
    `Given the new 'Symbol' primitive in ES6, is it possible to modify/set the behavior of a for...of loop in an object?` what has one got to do with the other? I'm lost – Jaromanda X Mar 02 '18 at 08:46
  • @JaromandaX `Symbol` extends the ability to do metaprogramming, which I figured might be the key to solving this (e.g through perhaps modifying Symbol.iterator) – sookie Mar 02 '18 at 08:49
  • Symbols can't be modified - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/iterator - oh, wait, I think I musunderstood what you mean by modifying Symbol.iterator ... – Jaromanda X Mar 02 '18 at 08:55
  • Yes, I should've been more precise. By "modifying `Symbol.iterator`," I meant modifying the generator function bound to this property on an object (e.g what can be seen here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/iterator ) – sookie Mar 02 '18 at 08:59

1 Answers1

3

well, you could do something like this (usual warnings about global prototypes apply):

Object.prototype[Symbol.iterator] = function* () {
    yield* Object.values(this);
};

x = {a: 1, b: 2}

for (let y of x)
    console.log(y)

or

Object.defineProperty(
    Object.prototype,
    Symbol.iterator, {
        value: function*() {
            yield* Object.values(this);
        }
    }
);

myObj = {x: 1, y: 2}
console.log([...myObj])

However, a cleaner solution would be to introduce a generic wrapper that would enable iteration for objects that are not iterable per se:

let iter = function* (a) {
    if (a === null || a === undefined)
        return;

    if (!a[Symbol.iterator]) {
        if (typeof a === 'object')
            a = Object.values(a);
        else
            a = [a];
    }

    for (let x of a)
        yield x;
};

console.log([...iter('abcd')])
console.log([...iter(123)])
console.log([...iter({a: 1, b: 2})])

And then simply use for (x of iter(whatever)) in your code.

georg
  • 211,518
  • 52
  • 313
  • 390
  • Quick question: Why does your first solution (modifying Object.prototype directly) work, however `Object.defineProperty(Object.prototype, Symbol.iterator, { value: function*() { ... } })` not work? – sookie Mar 02 '18 at 09:41
  • 1
    IIRC, `defineProperty` enforces `toString` on the second argument. – georg Mar 02 '18 at 09:44
  • Hmm.. seems like symbol properties are definable with `Object.defineProperty()` - https://stackoverflow.com/questions/31029612/why-can-es6-symbol-properties-be-made-enumerable-by-object-defineproperty – sookie Mar 02 '18 at 13:12
  • @sookie: yep, I was wrong about that, works fine actually (see update). – georg Mar 02 '18 at 15:09