1. 程式人生 > >瞭解ES6中的Iterator迭代器

瞭解ES6中的Iterator迭代器

寫在前面

迭代器是帶有特殊介面的物件,用以為不同的資料結構提供統一的訪問機制
由於目前ES6版本有4種可遍歷的“集合”(Array,Object,Map,Set,其中三種自帶預設迭代器),所以擁有Iterator之後一切都變的簡單了許多。我們只需要為其部署Iterator介面即可遍歷

遍歷過程

  • 建立一個指標物件,指向當前資料結構的起始位置
  • 第一次呼叫只針對象的next方法,將指標指向資料結構的第一個成員
  • 。。。。
  • 直至資料結構的結束位置
    每次呼叫next方法都會返回資料結構的當前成員資訊,即一個包含value和done兩個屬性的物件。value為對應成員值,而done則表示遍歷是否結束

在網上找了一段ES5模擬Interator的程式碼:

function createIterator(ary) {
  var i = 0;
  return {
    next: function() {
      return {
        value: ary[i++],
        done: i > ary.length
      }
    }
  }
}
var iterator = createIterator(['a', 'b', 'c'])
var done = false;

while (!done) {
  var result = iterator.next();
  console.log(result);
  done = result.done;
}
//{ value: 'a', done: false }
//{ value: 'b', done: false }
//{ value: 'c', done: false }
//{ value: undefined, done: true }

Ps:這裡只是模擬了Array資料型別的遍歷,真正的Iterator會有一個型別識別的(小聲BB)

有沒有讓大家想起當年學習C語言時候的連結串列?用指標把一串地址串聯起來,依次訪問下一個元素,這也是迭代器的本質

可迭代型別

由於ES6種引入了新的Symbol物件(唯一性),定義了一個Symbol.iterator屬性,所以只要物件中含有這個屬性那麼都是可迭代的。之所以前邊提到的四種集合均可迭代,就是因為他們都有預設的迭代器

讓我們舉一個栗子:

let ary = ['a', 'b', 'c'];
let Iterator = ary[Symbol.iterator]();
console.log(iterator.next()); //{ value: 'a', done: false }
console.log(iterator.next()); //{ value: 'b', done: false }
console.log(iterator.next()); //{ value: 'c', done: false }
console.log(iterator.next()); //{ value: undefined, done: true }

如圖,我們將陣列物件的預設iterator的值賦給了let出的新的Iterator值,並且通過next不斷向後取值。不過這個是ES6自帶的迭代器有些老鐵看了可能不服氣。那咱們自己搞一個不自帶迭代器的型別:Object

const obj = {
   b: 2
 }
 const a = 'a'
 obj.a = 1;
 Object.defineProperty(obj, Symbol.iterator, {
   enumerable: false,
   writable: false,
   configurable: true,
   value: function () {
     const that = this;		//儲存指標
     let index = 0;
     const ks = Object.keys(that);	
     return {
       next: function() {
         return {
           value: that[ks[index++]],
           done: (index > ks.length)
         }
       }
     }
   }
 })
 for(const v of obj) {
   console.log(v); //  2 , 1
 }

遍歷之時我們要做的就是通過Object.keys去除他的key值,然後通過next向後查詢。由於它被設定了[Symbol.iterator]屬性,所以for…of可以找到並呼叫

對於類似於陣列的物件,即存在數值鍵名以及length屬性,部署Iterator介面有一個更加簡便的方法:使用Symbol.iterator方法直接引用陣列的Iterator介面

NodeList.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];
NodeList.prototype[Symbol.iterator] = [][Symbol.iterator];

上面兩種方法都可以實現。NodeList物件是類似陣列的物件,本來就具有遍歷介面,可以直接遍歷。上圖我們將其便利介面改成陣列的Symbol.iterator屬性不會有任何影響

事實上,Symbol.iterator屬性對應一個函式,執行後返回當前物件的遍歷器物件。本質上還是通過指標結構的遍歷。我們最後再把它的指標結構模擬一下

function Obj(value){
	this.value = value;
	this.next = next;
}
Obj.prototype[Symbol.iterator] = function(){
	var iterator = { next: next };
	var current = this;
	function next(){
		if(current){
			var value = current.value;
			current = current.next;
			return {done: false, value: value}; 
		}else{
			return {done: true};
		}
	}
	return iterator
}

var one = new Obj(1);
var two = new Obj(2);
var three = new Obj(3);

one.next = two;
two.next = three;

for(var i of one){
	console.log(i);	///1,2,,3
}

上邊程式碼我們首先在原型鏈上部署Symbol.iterator方法,呼叫該方法會返回遍歷器物件iterator,呼叫該物件的next方法,在返回一個值得同時自動將內部指標移到下一個例項

Import Points

經典用法。

當資料集合非常大的時候,使用iterator可以幫助我們省去很多麻煩。

const fibonacci = {
   [Symbol.iterator]: function () {
     let [pre, next] = [0, 1];
     return {
       next() {
         [pre, next] = [next, pre + next];
         return {
           value: next,
           done: next > 1000
         }
       }
     }
   }
 }
 
 for(var n of fibonacci) {
   console.log(n)
 }
 // 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610,987

Ps:[Symbol.iterator]是一個預定義好的,Symbol型別的特殊值
此值為value和done組成的鍵值對
我們舉一個阮一峰老師的栗子

let arr = ['a', 'b', 'c'];
let iter = arr[Symbol.iterator]();

iter.next() //{value:'a', done:false}
iter.next() //{value:'b', done:false}
iter.next() //{value:'c', done:false}
iter.next() //{value:undefined, done:true}

擴充套件運算子(…)

擴充套件運算子(…)也會呼叫預設的Iterator介面

var str = 'hello';
[...str]		//['h', 'e', 'l', 'l', 'o']

let arr = ['b', 'c'];
['a', ...arr, 'd']		//['a', 'b', 'c', 'd']

實際上這提供了一種簡便機制,可以將任何部署了Iterator介面的資料結構轉化為陣列
eg:let arr = […iterable];

yield*

yield*後面跟的是一個可遍歷結構,亦會呼叫該結構的遍歷器介面

let generator = function* (){
	yield 1;
	yield* [2,3,4];
	yield 5;
};

var iterator = generator();

iterator.next(); //{value: 1, donne:false};
iterator.next(); //{value: 2, donne:false};
iterator.next(); //{value: 3, donne:false};
iterator.next(); //{value: 4, donne:false};
iterator.next(); //{value: 5, donne:false};
iterator.next(); //{value: undefined, donne:true}

最後說兩句

Symbol.iterator方法的最簡單實現還是使用今後要介紹道的Generator函式,通過yield命令給出每一步的返回值,從而實現一個生成器。具體的話我們下一篇部落格再詳細說說吧