Try   HackMD

Javascript的for in及for of介紹

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

photo by Tine Ivanič on unsplash

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、forin以及forof,這幾個兄弟了,而今天我們要針對forin和forof做深入的介紹。

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裡繼承的方法也被輸出了。

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
注意:關於prototype的觀念,可以參考Huli寫的文章,這是一個超大坑。

為了避免此問題,網路上有一種通用解法,使用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迴圈。

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迴圈。