您好,登錄后才能下訂單哦!
這篇文章主要講解了“什么是for of和Iterator”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“什么是for of和Iterator”吧!
var arr = ['紅','綠','藍'];
上面是一個普通的數組,如果我要獲取他的每一項數據,應該怎么做?
這個還不簡單,直接來個 for循環,如果你覺得循環 low,那就來個 forEach 唄。
ok,立刻擼串代碼
//for 循環
for(var i=0;i<arr.length;i++){
console.log(arr[i]);
}
//forEacth
arr.forEach(item=>{
console.log(item);
});
// 結果 略
ok,沒毛病。
那咱們繼續,請看下面代碼。給定一個字符串,如果我想輸出每一個字符怎么做?
var str='1234567890';
有么有搞錯,這簡單的讓人發笑。
可以用 for in,也可以用 for 循環,按照類數組方式來處理。
立刻擼串代碼
//for in
for(var s in str){
console.log(str[s]);//s 是 屬性名稱【key】
}
//轉為數組
for(var i =0;i<str.length;i++){
console.log(str[i]);
}
//或者轉換為數組
Array.prototype.slice.call(str);
不過 for in
不是用來獲取數據的,他會遍歷對象上所有可枚舉的屬性,包括自身的和原型鏈上的,所以這個不保險。
emmm....沒啥問題,那咱們繼續。
請看下面代碼,給定一個map
對象,然后輸出它每一項數據。
var map = new Map();
map.set('zhang','1000w');
map.set('liu','200w');
map.set('sun','100w');
用 forEach
就妥妥的了。
map.forEach((val,key)=>{
console.log(val,key);
})
到這里看了這么多如此簡單到令人發指的提問,估計都有些坐不住了,要掀桌子走人了。請稍后,慢慢往下看。
好了,在上一步幾個簡單的問題中,我們的操作都是獲得他們的每一項數據。
當然方法有很多種,實現方式也有很多,for 循環
,forEach
,for in
啦。
但是有沒有發現一個問題,或者我們站在一個更高的維度去看待,其實這些方法都不能通用,也就是說上面的這幾種集合數據不能使用統一的遍歷方法來進行數據獲取。
誰說的,能統一呀,都可以用 forEach
來遍歷,數組和map
本身就支持,字符串我直接轉為數組后可以了。
ok,這沒什么毛病。
但是每次都要轉換,還要封裝,還有可能要侵入原型。
那有沒有一種更好的,通用方法,讓開發者用的更舒服,更爽呢?
答案是肯定的,es5
的時候還沒出現,升級到 es6
就有了。
這個可以對不同數據結構進行統一遍歷的方式就是 es6
的 for of
循環。
for of
循環 和 古老的for 循環
很像呀。不就是個新增語法么。
引用阮大佬書中一句話,相信一看便知。
ES6 借鑒 C++、Java、C# 和 Python 語言,引入了for...of循環。作為遍歷所有數據結構的統一的方法。
關鍵在于統一,另一個就是直接取值,簡化操作,不需要在聲明和維護什么變量、對數據做轉換。
原來for of
這么牛,for
循環搞不定的你可以搞定。
為什么 for of
能具備這個能力,可以為不同的數據結構提供一種統一的數據獲取方式。
for of
真的這么強大嗎,他背后的機制是什么?
其實for of
并不是真的強大,他只是一種ES6
新的語法而已。
并不是所有的對象都能使用 for of
,只有實現了Iterator
接口的對象才能夠使用 for of
來進行遍歷取值。
所以說 for of
只是語法糖,真正的主角是Iterator
。
What ? Iterator.....
Iterator
是一種接口,目的是為不同的數據結構提供統一的數據訪問機制。也可以理解為 Iterator
接口主要為 for of
服務的,供for...of
進行消費。
其實在很多后端語言多年前早已存在 Iterator 這個特性,如 java、C++、C#等。
既然他是一種接口,那我們應該怎樣實現這個接口呢?實現規則是什么樣的呢?
因為 javascript
語言里沒有接口的概念,這里我們可以理解成它是一種特殊的對象 - 迭代器對象,返回此對象的方法叫做迭代器方法。
首先他作為一個對象,此對象具有一個next
方法,每次調用 next
方法都會返回一個結果值。
這個結果值是一個 object
,包含兩個屬性,value
和 done
。
value
表示具體的返回值,done
是布爾類型的,表示集合是否遍歷完成或者是否后續還有可用數據,沒有可用數據則返回 true
,否則返回 false
。
另外內部會維護一個指針,用來指向當前集合的位置,每調用一次 next
方法,指針都會向后移動一個位置(可以想象成數組的索引)。
看下代碼實現
function getIterator(list) {
var i = 0;
return {
next: function() {
var done = (i >= list.length);
var value = !done ? list[i++] : undefined;
return {
done: done,
value: value
};
}
};
}
var it = getIterator(['a', 'b', 'c']);
console.log(it.next()); // {value: "a", done: false}
console.log(it.next()); // {value: "b", done: false}
console.log(it.next()); // {value: "c", done: false}
console.log(it.next()); // "{ value: undefined, done: true }"
console.log(it.next()); // "{ value: undefined, done: true }"
console.log(it.next()); // "{ value: undefined, done: true }"
上面代碼便是根據迭代器的基本概念衍生出來的一個模擬實現。
getIterator
方法返回一個對象 - 可迭代對象next
方法,
next
方法內部通過閉包來保存指針
i
的值,每次調用
next
方法
i
的值都會
+1
.i
的值從數組內取出數據作為
value
,然后通過索引判斷得到
done
的值。i=3
的時候,超過數組的最大索引,無可用數據返回,此時done 為
true
,遍歷完成。 到這里我們已經大概了解了 Iterator
, 以及如何創建一個迭代器對象。但是他和 for of
有什么關系呢?
當 for of
執行的時候,循環過程中引擎就會自動調用這個對象上的迭代器方法
, 依次執行迭代器對象的 next
方法,將 next
返回值賦值給 for of
內的變量,從而得到具體的值。
我覺得上面一句話包含了一個重要的信息- “對象上的迭代器方法”。
對象上怎么會有迭代器方法呢?
ES6
里規定,只要在對象的屬性上部署了Iterator
接口,具體形式為給對象添加Symbol.iterator
屬性,此屬性指向一個迭代器方法,這個迭代器會返回一個特殊的對象 - 迭代器對象。
而部署這個屬性并且實現了迭代器方法后的對象叫做可迭代對象
。
此時,這個對象就是可迭代的,也就是可以被 for of
遍歷。
Symbol.iterator,它是一個表達式,返回Symbol對象的iterator屬性,這是一個預定義好的、類型為 Symbol 的特殊值。
舉個例子
普通的對象是不能被 for of
遍歷的,直接食用會報錯。
var obj = {};
for(var k of obj){
}
obj 不是可迭代的對象。
那么我們來,讓一個對象變成可迭代對象,按照協議也就是規定來實現即可。
iterableObj
對象上部署 Symbol.iterator
屬性,然后為其創建一個迭代器方法,迭代器的規則上面我們已經說過啦。
var iterableObj = {
items:[100,200,300],
[Symbol.iterator]:function(){
var self=this;
var i = 0;
return {
next: function() {
var done = (i >= self.items.length);
var value = !done ? self.items[i++] : undefined;
return {
done: done,
value: value
};
}
};
}
}
//遍歷它
for(var item of iterableObj){
console.log(item); //100,200,300
}
就這么簡單,上面這個對象就是可迭代對象了,可以被 for of
消費了。
我們再回到最開始使用 for of
來進行遍歷字符串、數組、map,我們并沒有為他們部署Iterator
接口,仍然可以使用 for of
遍歷。
這是因為在 ES6
中有些對象已經默認部署了此接口,不需要做任何處理,就可以使用 for of
來進行遍歷取值。
不信?咿,你好難搞,我不要你說 - 信,我要我說 - 信。
看看能不能拿到它們的迭代器。
數組
//數組
var arr=[100,200,300];
var iteratorObj= arr[Symbol.iterator]();//得到迭代器方法,返回迭代器對象
console.log(iteratorObj.next());
console.log(iteratorObj.next());
console.log(iteratorObj.next());
console.log(iteratorObj.next());
字符串
因為字符串本身的值是有序的,并且具有類數組的特性,支持通過索引訪問,所以也默認部署了iterator
接口。
var str='abc';
var strIteratorObj = str[Symbol.iterator]();//得到迭代器
console.log(strIteratorObj.next());
console.log(strIteratorObj.next());
console.log(strIteratorObj.next());
console.log(strIteratorObj.next());
或者直接看原型上是否部署了這個屬性即可。
arguments
類數組
函數內的arguments
是一個類數組,他也支持 for of
,因為他內部也部署了Iterator
接口。
我們都知道對象是默認沒有部署這個接口的,所以arguments
這個屬性沒有在原型上,而在在對象自身的屬性上。
function test(){
var obj = arguments[Symbol.iterator]();
console.log( arguments.hasOwnProperty(Symbol.iterator));
console.log( arguments);
console.log(obj.next());
}
test(1,2,3);
總結來說,已默認部署 Iterator 接口的對象主要包括數組、字符串、Set、Map 、類似數組的對象(比如arguments對象、DOM NodeList 對象)。
代碼驗證略,都是一個套路,不多說。
Iterator
除了可以為不同的數據結構提供一種統一的數據訪問方式,還有沒有發現其他的作用?
那就是數據可定制性,因為我們可以隨意的控制迭代器的 value
值。
比如:數組本身就是一個可迭代的,我們可以覆蓋他的默認迭代器。
var arr=[100,200,300];
for(var o of arr){
console.log(o);
}
for of
數組默認輸出如下
經過我們的改造
var arr=[100,200,300];
arr[Symbol.iterator]=function(){
var self=this;
var i = 0;
return {
next: function() {
var done = (i >= self.length);
var value = !done ? self[i++] : undefined;
return {
done: done,
value: value
};
}
};
}
for(var o of arr){
console.log(o);
}
對象可能有各種屬性,不像數組的值是有序的。
所以遍歷的時候根本不知道如何確定他們的先后順序,所以需要我們根據情況手動實現。
我們都知道普通的 for
循環是可以隨時中斷的,那 for of
是否可以呢?
答案是肯定的,for of
機制兼顧了for
和forEach
。
迭代器除了必須next
方法外,還有兩個可選的方法 return
和throw
方法。
如果 for of
循環提前退出,則會自動調用 return
方法,需要注意的是 return
方法必須有返回值,且返回值必須是 一個object
。
ps:以拋出異常的方式退出,會先執行 return
方法再拋出異常。
var iterableObj = {
items:[100,200,300],
[Symbol.iterator]:function(){
var self=this;
var i = 0;
return {
next: function() {
var done = (i >= self.items.length);
var value = !done ? self.items[i++] : undefined;
return {
done: done,
value: value
};
},
return(){
console.log('提前退出');
return {//必須返回一個對象
done:true
}
}
};
}
}
for(var item of iterableObj){
console.log(item);
if(item===200){
break;
}
}
for(var item of iterableObj){
console.log(item);
throw new Error();
}
ps:throw 方法這里先不說,這里不是他的用武之地,后續文章見。
除了 for of
執行的時候會自動調用對象的Iterator
方法,那么ES6
里還有沒有其他的語法形式?
對可迭代對象進行解構賦值的時候,會默認調用Symbol.iterator
方法。
//字符串
var str='12345';
var [a,b]=str;
console.log(a,b); // 1 2
//map
var map = new Map();
map.set('我','前端');
map.set('是','技術');
map.set('誰','江湖');
map.set('作者','zz_jesse');
var [d,e]=map;
console.log(d,e);
//["我", "前端"] ["是", "技術"]
....
同樣如果對一個普通對象進行解構,則會報錯。
因為普通對象不是可迭代對象。
var [d,e]={name:'zhang'};
從一個自定義的可迭代對象進行解構賦值。
var iterableObj = {
items:['紅','綠','藍'],
[Symbol.iterator]:function(){
var self=this;
var i = 0;
return {
next: function() {
var done = (i >= self.items.length);
var value = !done ? self.items[i++] : undefined;
return {
done: done,
value: value
};
}
};
}
}
var [d,e]=iterableObj;
console.log(d,e);//紅 綠
解構賦值的變量的值就是迭代器對象的 next 方法的返回值,且是按順序返回。
擴展運算符的執行(...)也會默認調用它的Symbol.iterator
方法,可以將當前迭代對象轉換為數組。
//字符串
var str='1234';
console.log([...str]);//[1,2,3,4] 轉換為數組
//map 對象
var map=new Map([[1,2],[3,4]]);
[...map] //[[1,2],[3,4]
//set 對象
var set=new Set([1,2,3]);
[...set] //[1,2,3]
通用普通對象是不可以轉為數組的。
var obj={name:'zhang'};
[..obj]//報錯
作為一些數據的數據源,比如某些api 方法的參數是接收一個數組,都會默認的調用自身迭代器。
例如:Set、Map、Array.from 等
//為了證明,先把一個數組的默認迭代器給覆蓋掉
var arr=[100,200,300];
arr[Symbol.iterator]=function(){
var self=this;
var i = 0;
return {
next: function() {
var done = (i >= self.length);
var value = !done ? self[i++] : undefined;
return {
done: done,
value: value+'前端技術江湖' //注意這里
};
}
};
}
for(var o of arr){
console.log(o);
}
// 100前端技術江湖
// 200前端技術江湖
// 300前端技術江湖
已覆蓋完成
//生成 set 對象
var set = new Set(arr);
//調用 Array.from方法
Array.from(arr);
yield*后面跟的是一個可遍歷的結構,執行時也會調用迭代器函數。
let foo = function* () {
yield 1;
yield* [2,3,4];
yield 5;
};
yield 需要著重說明, 下一節再詳細介紹。
既然可迭代對象的規則必須在對象上部署Symbol.iterator
屬性,那么我們基本上就可以通過此屬來判斷對象是否為可迭代對象,然后就可以知道是否能使用 for of
取值了。
function isIterable(object) {
return typeof object[Symbol.iterator] === "function";
}
console.log(isIterable('abcdefg')); // true
console.log(isIterable([1, 2, 3])); // true
console.log(isIterable("Hello")); // true
console.log(isIterable(new Map())); // true
console.log(isIterable(new Set())); // true
console.log(isIterable(new WeakMap())); // false
console.log(isIterable(new WeakSet())); // false
ES6
的出現帶來了很多新的數據結構,比如 Map
,Set
,所以為了數據獲取的方便,增加了一種統一獲取數據的方式 for of
。而 for of
執行的時候引擎會自動調用對象的迭代器來取值。
不是所有的對象都支持這種方式,必須是實現了Iterator
接口的才可以,這樣的對象我們稱他們為可迭代對象。
迭代器實現方式根據可迭代協議,迭代器協議實現即可。
除了統一數據訪問方式,還可以自定義得到的數據內容,隨便怎樣,只要是你需要的。
迭代器是一個方法, 用來返回迭代器對象。
可迭代對象是部署了 Iterator
接口的對象,同時擁有正確的迭代器方法。
ES6內很多地方都應用了Iterator
,平時可以多留意觀察,多想一步。
到這里我們已經可以根據迭代器的規則自定義迭代器了,但實現的過程有些復雜,畢竟需要自己來維護內部指針,有不少的邏輯處理,難免會出錯。
那有沒有更優雅的實現方式呢?
有,那就是-Generator -生成器 。
let obj = {
* [Symbol.iterator]() {
yield 'hello';
yield 'world';
}
};
for (let x of obj) {
console.log(x);
}
// "hello"
// "world"
可以看出它非常簡潔,無需過多代碼就可以生成一個迭代器。
它除了可以作為生成迭代器的語法糖,他還有更多神奇的能力。
這次就先搞定Iterator
,下次搞 Generator
。
感謝各位的閱讀,以上就是“什么是for of和Iterator”的內容了,經過本文的學習后,相信大家對什么是for of和Iterator這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。