# Javascript的for in及for of介紹 ![](https://i.imgur.com/7izgWAu.jpg) photo by [Tine Ivanič](https://unsplash.com/@tine999) on [unsplash](https://unsplash.com/photos/u2d0BPZFXOY) >If you cannot explain what you are attempting to accomplish to a sixyear-old child, then you are probably not clear about it yourself. -Albert Einstein --- ## 前言 Javascript裡的for迴圈家族,不外乎就是for、forEach、for...in以及for...of,這幾個兄弟了,而今天我們要針對for...in和for...of做深入的介紹。 <br/> ## for in迴圈基本介紹 for in這個語法是在ES5就已經出現的語法,而他可以針對物件(Object)以及陣列(Array)做遍歷,來看看以下的程式碼範例。 ``` //針對物件: let jason = { firstName: 'Jason', lastName: 'Tung' } for (let prop in jason) { console.log(prop + ': ' + jason[prop]); } // firstName: Jason // lastName: Tung //針對陣列: let arr = [10, 202, 383]; for (let ele in arr) { console.log(ele); } // 0 // 1 // 2 ``` 首先在物件的範例中,我們有一個名為Jason的物件,接著我們透過自定義的prop,存取物件裡的屬性(property),而for in迴圈會遍歷物件裡所有的屬性,直到所有屬性都遍歷完成,所以在這個範例,他會遍歷兩次,把firstName和lastName都console出來;在陣列的範例中,for in迴圈則會把陣列裡的所有元素(element)的索引值(index)給遍歷出來。 ## for in迴圈奇怪的部分 接著我們在剛剛的物件和陣列增加一個屬性foo,value帶上bar,迴圈依然正常的輸出。 ``` //針對物件: jason.foo = 'bar'; for (let prop in jason) { console.log(prop + ':' + jason[prop]); } // firstName:Jason // lastName:Tung // foo:bar //針對陣列: arr.foo = 'bar'; for (let ele in arr) { console.log(ele); } // 0 // 1 // 2 // foo ``` ### 1. 物件Prototype繼承之問題 而如果我們是透過函式建構式(function constructor)來創造物件時,物件可能會繼承含是建構式的一些屬性和方法,這時如果直接用for in迴圈時,繼承的屬性和方法也會一起被輸出。 ``` // function constructor,建構一個Person物件 let Person = function (firstName, lastName) { this.firstName = firstName; this.lastName = lastName; } // function constructor 的 prototype Person.prototype.getFullName = function () { return this.firstName + ' ' + this.lastName; } // function constructor建立的物件 Person1 let Person1 = new Person('Jason', 'Tung'); // for...in輸出 for (let prop in Person1) { console.log(prop + ': ' + Person1[prop]); } // firstName: Jason // lastName: Tung // getFullName:function(){return this.firstName + ' ' + this.lastName;} ``` 可以發現在prototype裡繼承的方法也被輸出了。 :::warning :zap: 注意:關於prototype的觀念,可以參考[Huli寫的文章](https://blog.techbridge.cc/2017/04/22/javascript-prototype/),這是一個超大坑。 ::: 為了避免此問題,網路上有一種通用解法,使用Object.prototype.hasOwnProperty,透過這個方法可以檢查這個屬性是否是直接或是繼承的,他會回傳一個布林值,當是true的時候,表該屬性是直接的。 ``` for (let prop in Person1) { if (Person1.hasOwnProperty(prop)) { console.log(prop + ': ' + Person1[prop]); } } // firstName: Jason // lastName: Tung ``` 當然還有一種方法,就是用Object.keys(),但是他會回傳一個陣列,裡面的元素這個物件會回傳一個陣列,裡面的元素會是這個物件裡的所有屬性,且不包含繼承的屬性;同樣的,如果你要取物件裡所有的值,也可以使用Object.values(),這個方法會回傳一個陣列,裡面的元素會是這個物件裡的所有值,且也不包含繼承下來的值。 ``` let keyArr = Oject.keys(Person1); console.log(keyArr); // ['firstName', 'lastName'] let valueArr = Object.values(Person1); console.log(valueArr); // ['Jason', 'Tung'] // 輸出key value for (let i = 0; i < keyArr.length; i++) { console.log(keyArr[i] + ': ' + Person1[keyArr[i]]); } // firstName: Jason // lastName: Tung ``` ### 2. 陣列Prototype繼承問題 同樣的,陣列在使用for in迴圈時,也有上述會把繼承的方法或屬性輸出的情況。 ``` Array.prototype.job = 'front-end developer'; let arr1 = ['cat', 'dog', 'human']; for (let prop in arr1) { console.log(prop + ': ' + arr1[prop]) } // 0: cat // 1: dog // 2: human // job: front-end developer ``` 為了避免此問題發生,我們可以直接用一般的for迴圈就可以了。不過我們也可以直接用for of迴圈和Object.entries(),for of迴圈稍後會再詳細介紹一下。 ``` // 一般for迴圈 for (let i = 0; i < arr1.length; i++) { console.log(i + ': ' + arr1[i]); } // 0: cat // 1: dog // 2: human for (let [key, value] of Object.entries(arr1)) { console.log(key + ": " + value); } // 0: cat // 1: dog // 2: human ``` --- ### 小結 所以根據以上for in奇怪的部分,我們在處理有繼承屬性的物件或陣列時,應該避免去使用for in迴圈,以防產生預期之外的狀況。取而代之的是,你可以好好利用Object.keys()、Object.values()、Object.entries()結合一般的for迴圈處理物件;至於陣列的處理,就輪到我們接下來要講的for of迴圈。 <br /> ## for of迴圈基本介紹 終於,ES6裡面出現了for of迴圈的語法,它主要彌補了for迴圈家族的不足處,你可以利用它來處理迭代(iterable)的資料,像是陣列、字串、set、map、型別陣列等等。 看以下範例: ``` // 陣列的for of let arr = [1, 2, 3]; for(let value of arr){ console.log(value); } // 1 // 2 // 3 //字串的for of let name = 'Jason'; for(let value of name){ console.log(value); } // J // a // s // o // n //set的for of let set = new Set([1, 2, 3, 4, 5]); for(let value of set){ console.log(value); } // 1 // 2 // 3 // 4 // 5 //map的for of let map = new Map(); map.set('name', 'Jason'); map.set('job', 'engineer'); for (var [key, value] of map) { console.log(key + ': ' + value); } // name: Jason // job: engineer ``` ### 小結 剛剛也說到for of補足了以往for迴圈家族的一些缺點,例如:forEach無法使用continue、break、return等等語句,但for of可以支持;for of也解決上面所講的繼承問題;for of的語法也比一般for迴圈寫法更加簡潔。而且最棒的是他支援相當多的迭代類型資料。 所以當你以後遇到要處理迭代資料且需要用到迴圈時,不妨可以試試for of迴圈。