0

I am currently reading about Symbols and Iterators (ES6 features) and after running into an example there, I am trying to make an object iterable so that I can use for...of feature. From few examples/answers here/articles I checked, it looks like this in plain case:

let obj = {
  prop1: 5,
  prop2: 'test',
  prop3: new Date(),
  [Symbol.iterator]: () => ({
  items: obj.items,
  next: function next() {
    return {
        done: this.items.length === 0,
        value: this.items.shift()
      }
  }
  })
};
Object.defineProperty(obj, "items", {
  enumerable: false,
  get: function() {
      let props = [];
      for(let prop in this) if(this.hasOwnProperty(prop)) props.push(this[prop]);
      return props;
  }
});
for(let prop of obj) console.log(prop);

But I find it annoying to list manually all the values of an object's properties in items array of iterator. Also it feels dirty and messy with Object.defineProperty. I am kind of trying tom expand the example from the link. Is there a smarter/simpler way to get all the object's properties inside iterator (instead of items: obj.items and related bloat or manually listing items like items: [5, 'test', new Date()])?

curveball
  • 4,320
  • 15
  • 39
  • 49
  • Why did you think you needed an `.items` getter property for this? – Bergi Jul 14 '17 at 11:55
  • well, first off, I needed to set `enumerable: false` to avoid appearing this property itself in the list; also to make the object cleaner. – curveball Jul 14 '17 at 11:57
  • 2
    Don't make objects iterable, there's no point. Instead, just write `for (let prop in obj) console.log(prop);` - that's what `for … in` loops are good for! – Bergi Jul 14 '17 at 11:58
  • No, why did you think you needed `obj.items` at all? – Bergi Jul 14 '17 at 11:59
  • I needed it to get the array of actual values for iterator method. – curveball Jul 14 '17 at 12:05
  • sure, you are right about `for...in` being useful for objects. I just tried to implement Symbol.iterator stuff with learning purpose. – curveball Jul 14 '17 at 12:06

3 Answers3

2

You could return a generator in the symbol iterator maybe:

[Symbol.iterator]:function*(){
    for(value of Object.values(this)){
      yield value;//may extend this to your needs
    }
 }

Or in your case:

[Symbol.iterator]:function*(){
  var i=1;
  while(this["prop"+i]){
      yield this["prop"+i];//may extend this to your needs
      i++;
  }
}

http://jsbin.com/zekunalusi/edit?console

Jonas Wilms
  • 132,000
  • 20
  • 149
  • 151
  • 1
    That creates infinite recursion as `of` tries to iterate the object… – Bergi Jul 14 '17 at 11:59
  • @bergi oh right, does Object.values() use the iterator (i hope not)? – Jonas Wilms Jul 14 '17 at 12:00
  • That'll work, `Object.values` enumerates the properties – Bergi Jul 14 '17 at 12:01
  • I tried your second alternative. It says `Uncaught ReferenceError: regeneratorRuntime is not defined` – curveball Jul 14 '17 at 12:12
  • sorry, it works on jsbin, but fails on codepen where I test – curveball Jul 14 '17 at 12:14
  • @curveball i bet its a problem in your babel environment https://stackoverflow.com/questions/33527653/babel-6-regeneratorruntime-is-not-defined-with-async-await – Jonas Wilms Jul 14 '17 at 12:14
  • @Jonas w I like the idea with generators since they return iterator object which is expected by `for...of`. Although in this case the whole thing seems to be senseless because it is easier to use `for...in` from the beginning. – curveball Jul 14 '17 at 12:22
  • @curveball yeah i think there are very rare usecases for generators and iterators, and this isnt one in my opinion ( you could simply do for(value in obj.items), but thats another question ;) – Jonas Wilms Jul 14 '17 at 12:24
  • @Jonas w yeah, totally agree. I just was reading an article with such an example of usecase. Check it out if interested: https://ponyfoo.com/articles/es6-iterators-in-depth#iterator-protocol-and-iterable-protocol – curveball Jul 14 '17 at 12:26
1

You should not use an items getter but instead just create the array inside the iterator method. Also you can use generator syntax to create the iterator, which is much easier.

But the simplest way to achieve what you want is

let obj = {
  prop1: 5,
  prop2: 'test',
  prop3: new Date(),
  [Symbol.iterator]() { return Object.keys(this).map(k => this[k])[Symbol.iterator](); }
};
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • when I tried this with `for..of` I got `Object.values(...).values is not a function` error. – curveball Jul 14 '17 at 12:03
  • Oh, right, [`Object.values`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/values) is still experimental. I'll update – Bergi Jul 14 '17 at 12:06
  • now it says `Uncaught TypeError: Object.keys(...).map(...).values is not a function` – curveball Jul 14 '17 at 12:08
  • @curveball Apparently browsers are lagging behind to support [`values`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/values) because of compat problems. Fortunately the same method exists under a different name :-) – Bergi Jul 14 '17 at 12:55
1
function objectEntries(obj) {
    let index = 0;

    // In ES6, you can use strings or symbols as property keys,
    // Reflect.ownKeys() retrieves both
    let propKeys = Reflect.ownKeys(obj);

    return {
        [Symbol.iterator]() {
            return this;
        },
        next() {
            if (index < propKeys.length) {
                let key = propKeys[index];
                index++;
                return { value: [key, obj[key]] };
            } else {
                return { done: true };
            }
        }
    };
}

let obj = { first: 'Jane', last: 'Doe' };
for (let [key,value] of objectEntries(obj)) {
    console.log(`${key}: ${value}`);
}

http://2ality.com/2015/02/es6-iteration.html

zloctb
  • 10,592
  • 8
  • 70
  • 89