文章首發javascript
本文已經默認你已經知道generator
是什麼以及for...of
和數據類型map
怎麼用了。html
先來一道思考題:java
經過下面的變量ios
const students = {
xiaohong: {
age: '22',
fav: ['sleep', 'basketball'],
teachers: {
english: 'daming',
chinense: 'helios',
math: ['helios2', 'helios3']
}
},
xiaoming: {
age: '22',
fav: ['sleep', 'basketball', 'football'],
teachers: {
english: 'daming',
chinense: 'helios',
math: ['helios2', 'helios3']
}
},
}
複製代碼
對於第一個問題來講,咱們可使用各類循環語句: for/whilegit
for (let i =0 ;i < students[xiaoming].fav.length; i++) {
if (students[xiaoming].fav[i] === 'basketball') console.log(true)
}
let i = 0;
while(i++ < students[xiaoming].fav.length) { if (students[xiaoming].fav[i] === 'basketball') console.log(true) } 複製代碼
for...ofes6
for (let item of students[xiaoming].fav ) {
if (item === 'basketball') console.log(true)
}
複製代碼
那麼對於第二個問題來講,由於for/while
是不能遍歷對象的,因此行不通,可是對象有一個專屬的遍歷方法 for...in
github
咱們來看一下怎麼經過for...in來遍歷:數組
for (let stu in students) {
console.log(stu)
}
複製代碼
你可能會想了,經過for...in
去遍歷數組會怎樣呢? 咱們看一下經過for...in
去遍歷:網絡
for (let item in students[xiaoming].fav) {
console.log(item)
// 或者去判斷
}
複製代碼
哎呀,經過for...in
不也照樣能實現數組的遍歷麼,那爲何不歸結到數組的遍歷裏面去呢! 這裏面還有一些細節須要去了解(這也是上面的「對象有一個專屬的遍歷方法」爲何加粗),咱們經過一段代碼去解釋:數據結構
const num = [5, 6, 7]
for (let i in num) {console.log(i + 1)}
// 01
// 11
// 21
複製代碼
這是由於for-in
是爲普通對象({key: value})設計的,因此只能遍歷到字符串類型的鍵。
還有下面這個雖然不經常使用,可是也是不得不說的:
const arr = [5, 6, 7]
arr.foo = function() {}
for (let i in arr) {
console.log(i)
}
// 5
// 6
// 7
// foo !!!
複製代碼
foo
屬於arr上面的方法,被遍歷出來是說的過去的。
那麼用for...of
咱們來看看會怎麼樣
for (let stu of students){}
// Uncaught TypeError: students is not iterable
複製代碼
is not iterable,這個iterable
是神馬東西,咱們接下來下面一步步的看。
iterable是ES6對iteration(迭代/遍歷)引入的接口。
若是一個對象被視爲iterable(可迭代)那麼它必定有一個Symbol.iterator
屬性,這個屬性返回一個iterator(迭代器)方法,這個方法返回一個規定的對象(這個後面會說)。也就是說iterable
是iterator
的工廠,iterable
可以建立iterator
。iterator
是用於遍歷數據結構中元素的指針。
Axel Rauschmaye大神的圖簡直不能再清晰了。
數據消費者: javascript自己提供的消費語句結構,例如for...of循環和spread operator (...) 數據源: 數據消費者可以經過不一樣的源(Array,string)獲得供數據消費者消費的值;
讓數據消費者支持全部的數據源這是不能夠行的,由於還可能增長新的數據消費者和數據源。所以ES6引入了Iterable
接口數據源去實現,數據消費者去使用
可迭代協議(iterable protocol) 是容許js對象可以自定義本身的迭代行爲。
簡單的說只要對象有Symbol.iterator
這個屬性就是可迭代的,咱們能夠經過重寫(一些對象實現了iterable,好比Array,string)/添加(對於沒有實現iterable的對象好比object,能夠添加這個屬性,使之變爲可迭代的)該熟悉使之變爲可迭代的。
當一個對象須要被迭代(for...of 或者 spread operator )的時候,他的Symbol.iterator
函數被調用而且無參數,而後返回一個迭代器。
迭代器協議定義了一種標準的方式來產生一個有限或無限序列的值。
當一個對象被認爲是一個迭代器的時候,它會至少實現next()
方法,next()
方法返回兩個屬性value
(d迭代器返回的值)和done
(迭代時候已經結束)。
還有幾個可選的方法能夠被實現,具體請看:sec-iterator-interface
再文章的最開始咱們已經說了再前ES6的時候,如何去遍歷。 如今咱們說說ES6新增的for...of
的做用。
在前面也已經說了,在ES6以前遍歷object的時候用for...in
循環,for...in
會遍歷對象上全部可枚舉的值(包括原型(prototype)上的屬性),好比下面這樣:
function foo() {
this.name = 'helios'
}
foo.prototype.sayName = function() {
return this.name;
}
var o = new foo();
for (var i in o) {
console.log(i)
}
// name
// sayName
複製代碼
若是咱們只想遍歷對象自身的屬性,可使用hasOwnProperty
,以下:
function foo() {
this.name = 'helios'
}
foo.prototype.sayName = function() {
return this.name;
}
var o = new foo();
for (var i in o) {
if (!o.hasOwnProperty(i)) continue;
console.log(i)
}
複製代碼
若是咱們不想讓一個對象的屬性,在for...in
中不被遍歷出來,但是使用Object.defineProperty
來定義對象上的屬性是否可別枚舉(更多的屬性請看:Object.defineProperty()),具體以下面代碼:
var obj = {name: 'helios'}
Object.defineProperty(obj, 'age', {
enumerable: false
})
for (var i in obj) {
console.log(i)
}
複製代碼
在這一小節的最後咱們來講說for...in
中的in操做符的含義:
prop in obj
:
咱們來看一組例子:
var o = {
name: 'heliso'
}
console.log('name' in o) // true
console.log(Symbol.iterator in o) // false
console.log('toString' in o) // true
複製代碼
這個操做符雖然也適用於數組,可是儘可能仍是不要用在數組中,由於會比較奇怪,以下代碼:
var arr = [6, 7,8]
console.log(7 in arr) // false
console.log(1 in arr) // true
console.log('length' in arr) // true
複製代碼
主要是前兩個比較奇怪對不對,由於對於數組prop
表明的是數組的索引而爲其存在的值。 按照這樣的思路,正在看的讀者你能思考一下in
操做符在字符串中是怎麼的模式麼?
只要是實現了Interable
接口的數據類型都能被遍歷。
javascript內部實現的有:
並非全部的iterable內容都來源於數據結構,也能經過在運行中計算出來,例如全部ES6的主要數據結構有三個方法可以返回iterable對象。
那就數據結構(數據源)去實現iterable就能夠了。
用通俗的話說就是,你若是要遍歷一個對象的話,有一下幾個步驟:
Symbol.iterator
那就去實現Symbol.iterator
函數要返回一個iterator
iterator
返回一個對象,對象中至少要包含一個next方法來獲取value
和done
上面咱們已經鋪墊了這麼多了,咱們說了javascript中object是不能被迭代了,由於沒有實現iterable
,如今讓咱們來實踐一下讓object變的可迭代。
下面這樣寫確定是不行的
const obj = {
name: 'helios',
age: 23
}
for (let it of obj) {
console.log(it)
}
// TypeError: obj is not iterable
複製代碼
const obj = {
name: 'helios',
age: 23,
[Symbol.iterator]: function() {
let age = 23;
const iterator = {
next() {
return {
value: age,
done: age++ > 24
}
}
}
return iterator
}
}
複製代碼
若是iterable
和iterable
是一個對象的話,上面的代碼能夠簡化爲:
function iterOver() {
let age = 23;
const iterable = {
[Symbol.iterator]() {return this},
next() {
return {
value: age,
done: age++ > 24
}
}
}
return iterable
}
for (let i of iterOver()) {
console.log(i)
}
複製代碼
咱們若是每次想把一個不能迭代的對象變爲可迭代的對象,在實現Symbol.iterator
的時候,每次都要寫返回一個對象,對象裏面有對應的next方法,next方法必須返回valua和done兩個值。
這樣寫的話每次都會很繁,好在ES6提供了generator(生成器)能生成迭代器,咱們來看簡化後的代碼:
const obj = {
name: 'helios',
age: 23,
[Symbol.iterator]: function* () {
while (this.age <= 24) yield this.age++
}
}
for (let it of obj) {
console.log(it)
}
複製代碼
知乎的這個回答是頗有水平的了:爲何es6裏的object不可迭代?
在stackoverflow中也有很高質量的回答:Why are Objects not Iterable in JavaScript?
在上面的回答中從技術方面說了爲何Object不能迭代(沒有實現iterable),還說了以什麼樣的方式去遍歷Object是個難題,因此把如何迭代的方式去留給了開發者。
可是仍是要思考的一個問題就是:咱們真有必要去迭代對象字面量麼?
想一下咱們要迭代對象字面量的什麼呢?是keys
仍是values
亦或者是entries
,這三種方式在ES6提供的新的數據類型map裏面都有呀,徹底是能夠代替object的。在這裏不說object
和map
的區別,只是說說在ES6之後咱們想把兩個事物關聯起來的時候,不必定要非得是用對象字面量
了,map
支持的更好一下。
對於何時用對象字面量(object)何時使用map咱們能夠作一下總結:
對象字面量(object)應該是靜態的,也就是說咱們應該已經知道了裏面有多少個,和對象的屬性有什麼
使用對象字面量(object)的通常場景有:
JSON.stringify
和JSON.parse
時候其餘的狀況用map,其餘的狀況包括:
也並不說是map
就確定比對象字面量(object)好,map
也有以下的缺點:
JSON.stringify
/JSON.parse