Iterator & Iterable trong Javascript?

Tình hình mình mần về ES6 đang phân vân chưa hiểu sự khác nhau và cách dùng của 2 chú này. Ai chỉ mình với

Iterator là danh từ
Iterable là tính từ

Iterator là 1 đối tượng cụ thể
Iterable là khả năng của đối tượng

In JavaScript an iterator is an object which defines a sequence and potentially a return value upon its termination

An object is iterable if it defines its iteration behavior

Iterable là tính từ chỉ khả năng nên ta không dùng
Cách dùng Iterator : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols

2 Likes

JavaScript hay sử dụng Duck typing để nhận biết object thuộc kiểu dữ liệu nào.


Iterable là object có 1 property đặc biệt:

  • key có kiểu Symbol và có tên @@iterator, tên của key không thể truy xuất thông qua literal như string hay number, mà qua property iterator của Symbol object, hay Symbol.iterator
  • value có kiểu function, function không có tham số truyền vào

Đoạn code sau tạo biến iter dạng Iterable. Mình sử dụng cả 2 cách ES6 và ES5. ES6 sẽ nhanh hơn so với ES5 một chút.

var iterator = { ... };

// ES6
var iter = {
  [Symbol.iterator]: function () {
    return iterator;
  }
};

// ES5
var iter = {};
iter[Symbol.iterator] = function () {
  return iterator;
};

Thông thường Iterable sau khi gọi function object của property @@iterator thì sẽ trả về Iterator object. Tuy nhiên, JavaScript nhận diện kiểu duck typing, nên có thể trả về object bất kì. JavaScript chỉ biết Iterable là object có property là @@iterator và value của @@iterator có kiểu function.

Vì vậy cách nhận biết 1 object là Iterable được hiện thực gần giống như sau:

function isIterable(obj) {
  if (typeof obj !== 'object') {
    return false;
  }
  if (!(Symbol.iterator in obj)) {
    return false;
  }
  return typeof obj[Symbol.iterator] === 'function';
}

Iterator là object có 3 property có value thuộc kiểu function là: next, return, throw, nó sẽ có dạng tương tự như sau:

var iterator = {
  next: function (val) { /*...*/ },
  return: function (val) { /*...*/ },
  throw: function (val) { /*...*/ }
};

Function nhận biết object là Iterator như sau:

function isIterator(obj) {
  if (typeof(obj) !== 'object') {
    return false;
  }
  return !!(
    ('next' in obj && typeof obj.next === 'function') &&
    ('return' in obj && typeof obj.return === 'function') &&
    ('throw' in obj && typeof obj.throw === 'function')
  );
}

next của Iterator trả về object có 2 property là valuedone:

  • value có giá trị bất kì, nhưng thường dùng để liệt kê phần tử trong array-like object. Array-like object là object có các property dạng “0”, “1”, “2”,… Các property đều có property descriptor enumerabletrue.
  • done có giá trị true hoặc false, kiểu boolean. done thường được dùng thông báo bạn đã duyệt hết phần tử trong array-like object chưa. Nếu chưa trả về false, nếu đã duyệt hết thì trả về true.

return gọi tới next. Sau đó return sẽ thay đổi property done từ kết quả trả về của next sang true và trả về object đã thay đổi.

throw gọi tới next. throw không có trả về bất kì giá trị gì, nên throw trả về giá trị undefined. Ngoài ra, throw gọi lệnh throw với giá trị value của object trả về từ next.

Vì vậy object có dạng Iterator được hiện thực như sau:

var iterator = {
  next: function (val) {
    // stuff 
    var nextValue = ...
    // stuff
    var nextDone = ...
    // stuff
    return { value: nextValue, done: nextDone };
  },

  return: function (val) {
    var nextObj = this.next(val);
    nextObj.done = true;
    return nextObj;
  },

  throw: function (val) {
    var nextObj = this.next(val);
    throw nextObj.value;
  }
};

Ứng dụng của IterableIterator được dùng để hiện thực Iterator pattern.

Ví dụ mình tạo 1 array-like object có dạng Iterable để duyệt các phần tử từ 1 đến 6. Đoạn code bên dưới:

var numbersGen = {
  [Symbol.iterator]: function () {
    return {
      values: [1, 2, 3, 4, 5, 6],
      index: 0,

      next: function () {
        if (this.index < this.values.length) {
          return {
            value: this.values[this.index++],
            done: false
          };
        } else {
          return { done: true };
        }
      }
    };
  }
};

Đầu tiên, mình gọi @@iterator() đến Iterable để trả về Iterator

var numbers = numbersGen[Symbol.iterator]();

Sau đó, mình gọi tiếp các next() trong Iterator và in nó ra

var nextVal;
do {
  nextVal = numbers.next();
  console.log(nextVal.value);
} while (!nextVal.done);

Output

Bước gọi trên có thể viết gọn lại trong bằng lệnh for of. Cho kết quả tương tự

for (let value of numbersGen) {
  console.log(value);
}

for let gọi @@iterator của Iterable đứng đằng sau of. Sau đó gọi next() cho mỗi lần lặp. Nếu object trả về từ next() có property done trả về false thì thực hiện các câu lệnh trong block.

Điều kiện kết thúc của for of:

  • kiểm tra property done bằng true từ object trả về next() hoặc return()
  • catch exception từ throw()
7 Likes
83% thành viên diễn đàn không hỏi bài tập, còn bạn thì sao?