Symbol.iteratorでユーザクラスを反復可能にする

JavaScriptのユーザクラスをfor of構文でループ可能にする方法。Symbol.iteratoriteratorプロトコルを実装したオブジェクトを返す関数をセットするか、ジェネレータ関数を実装すれば良い。楽なのは後者

class Hoge {
  *[Symbol.iterator]() {
     yield 1;
     yield 2;
     yield 3;   
  }  
}

const o = new Hoge();

for (const i of o) {
  console.log(i)   // => 1, 2, 3    
}

Symbol.iteratorを使ってRangeクラスを作成。挙動はpythonのrange関数を参考にした。

class Range {
  constructor(begin = 0, end = 0, step = 1) {
    if (arguments.length === 0) {
      throw new Error('expected at least 1 arguments, got 0')
    } else if (arguments.length === 1) {
      end = begin;
      begin = 0;
    }
    this._begin = begin;
    this._end = end;
    this._step = step;
    if (step === 0) throw new Error('step argument must not be 0')
  }

  *[Symbol.iterator]() {
    if (this._step > 0) {
      for (let i = this._begin; i < this._end; i += this._step) yield i;
    } else {
      for (let i = this._begin; i > this._end; i += this._step) yield i;
    }
  }

  get length() {
    const len = Math.ceil((this._end - this._begin) / this._step);
    return len > 0 ? len : 0;
  }
}
const r = new Range(1, 9, 2);

for (const i of r) {
   console.log(i);           // => 1, 3, 5, 7
}

// 配列に変換
console.log(Array.from(r));  // [1 ,3, 5, 7]

// 分割代入
const [head, ...tail] = r;
console.log(head);           // 1
console.log(tail);           // [3, 5, 7]

// 分割代入で配列化
console.log(Array.of(...r));
console.log([...r]);

正直rangeはネイティブオブジェクトとして提供していいと思うのだが…