整篇會分成以下幾個部分:
map 這個 method 的全寫應該是 Array.prototype.map,有興趣可以看 Day 2 的介紹,這邊會直接使用 map() 作為替代。
Array method 有不少會使用到 callback function,如果尚不熟悉的話,可以看 Day 2 的介紹。
範例使用的 callback 都會使用箭頭函式做介紹,如果尚不熟悉的話可以參考 MDN 的介紹。
最後會透過分析 ECMAScript 來驗證是否有吻合,如果覺得 ECMAScript 有點艱澀難懂,我們在 Day 4 、Day 5 有介紹其相關術語可以幫助閱讀。
當你需要利用現有陣列產生一個新陣列、並不變動到原陣列時。
map() 會回傳一個 陣列 ,包含所有被 callback 操作過的原陣列元素。
map((element, index, array) => {
	/* 執行內容 */
})
map(callbackFn,thisArg)
map(function(element, index, array) {
	/* 執行內容 */
}, thisArg)
map() 的第 1 個參數為 callback, 第 2 個參數為選擇性 (optional) 的 thisArg。
callbackmap() 會按照陣列元素的順序依次 (升冪) 呼叫這個 callback,直到陣列元素被遍歷完畢,換句話說,如果這個陣列有 5 個元素,那這個 callback 至多會被呼叫 5 次。
當這個 callback 被呼叫時會帶入 element、index、array 三個參數。
這個 callback 回傳的值會被加到 map() 回傳的新陣列上。
element
陣列當前的元素 (element),callback 的第 1 個參數,為 map() 當前遍歷到的元素,也就表示 element 會依陣列的順序動態變化。
index
陣列當前元素的索引值 (index),callback 的第 2 個參數,為 map() 當前遍歷到的元素其索引值,也就表示 index 會依陣列的順序動態變化。
array
呼叫 map() 的陣列本身 (被遍歷的陣列本身), callback 的第 3 個參數,不論 map() 當下遍歷到哪個元素上, array 都會指向被遍歷的陣列本身,也就是呼叫 map() 的陣列本身。
thisArg為 map() 的第 2 個選擇性參數,它會被傳入 callback 並作為其 this 的值,如果沒有指定就會是 undefined。
請注意,如果 callback 使用箭頭函式的話則沒有作用!
可以參考範例的 Example - 4 寫法。
map() 會回傳一個跟原陣列相同長度的新陣列,包括所有經過 callback 操作的原陣列元素。
不會改動到原陣列。
const numbers = [1, 2, 3, 4, 5]
const doubleNumbers = numbers.map(number => number * 2)
console.log(doubleNumbers)
// [2, 4, 6, 8, 10]
numbers
numbers 呼叫 map() 時會順著原型鏈拿到放在 Array.prototype 指向的 prototype 物件上的 map() methodmap() 呼叫時需傳入了一個 callback,因此我們直接在 map() 的參數裡定義一個帶有 number 參數的箭頭函式map() 被呼叫時, map() 會執行帶入的 callback,並帶入 element、index、array 等 3 個參數,其中的 element 會被指派給 number 這個參數number 進行操作 (將其乘以 2) 後回傳,計算後的值會被推入新的陣列map() 回傳出來並指派給了 doubleNumbers
index 參數const names = ['Tyler', 'Emma', 'Arpad', 'Alexsander']
const namesWithIndices = names.map((name, index) => `${index + 1}. ${name}`)
console.log(namesWithIndices)
// ['1. Tyler', '2. Emma', '3. Arpad', '4. Alexsander']
index 參數, callback 必須依照順序定義參數,不能略過前面的參數const profiles = [
	{ name: 'Tyler', age: 25 },
	{ name: 'Emma', age: 21 },
	{ name: 'Arpad', age: 43 },
	{ name: 'Alexsander', age: 32 }
]
const simpleProfile = profiles.map(({ name, age }) => ({ [name]: age }))
console.log(simpleProfile)
// [
// 	{ Tyler: 25 },
// 	{ Emma: 21 },
// 	{ Arpad: 43 },
// 	{ Alexsander: 32 }
// ]
name 的值,屬性值為原本 age 的值thisArg 參數const names = ['Angela', 'Ross', 'Damien', 'Emma']
const genders = ['female', 'male', 'male', 'trans']
const profiles = names.map(function(name, index){
	return `${name} - ${this[index]}`
}, genders)
console.log(profiles)
//['Angela - female', 'Ross - male', 'Damien - male', 'Emma - trans']
genders 被作為 map() 的第 2 個參數傳入this 會指向 genders
請注意 callback 定義時的參數順序,依序應為 element、index、array,但不可隨意略過,就算你只想使用 index 而不需要 element, 可參考範例的 Example 2 寫法。
有一點值得注意的是,雖然 map() 不會變動到原陣列,但我們傳進去的 callback 卻有可能 ,而陣列元素被遍歷的範圍在第一次呼叫 callback 前就已經確立好了 (也就是 map() 被呼叫後但 callback 尚未被呼叫),因此有可能會發生以下的狀況:
undefined
上述這種高併發 (concurrent) 的更動會導致程式碼非常難以閱讀,非常不建議使用 (除非有不得已的特殊情境)。
Array.prototype.map(callbackfn[,thisArg])

this 轉型成一個 object 後指派給 O
O 的長度並指派給 len
callback 是不可呼叫的 object 則丟出一個 TypeError
O 作為原型創造出一個長度為 0 的陣列並指派給 A
k
k < len 時,重複以下步驟k 轉型成 Number 再轉型成 String 後指派給 PkO 是否有 Pk 這個屬性並將結果的布林值指派給 kPresentkPresent 為 true, 則進行以下步驟O 的 Pk 屬性的值,並指派給 kValuecallbackfn 並帶入 « kValue, ?(k), O » 這個 argumentList,並將回傳的值指派給 mappedValueA 上新增一個值為 mappedValue 的 Pk 屬性k = k + 1A
演算法的前 3 個步驟都是用來做一些前置處理,包括轉型、確認長度、確認參數是否為一個 function 等...。
步驟 4 則創造了一個類陣列,之後會被當作結果回傳。
步驟 6 開始執行迴圈,最大圈數為陣列長度,計數從 0 開始,重複以下步驟:
callbackfn 並帶入被取出的 元素、 當前計數、 陣列本身 這 3 個參數callbackfn 回傳的值,並將這個屬性新增至 A 這個新創造的類陣列上A。出現 ? 的地方代表有可能會丟出錯誤,所以整個演算法有 7 處有機會丟出錯誤,例如步驟 6 裡的 CreateDataPropertyOrThrow() 會在屬性新增失敗的時候丟出一個 TypeError。
如果出現 ! ,則代表這個 abstract operation 絕對不會丟出錯誤,例如步驟 7 的 ToString() 它會在參數是一個 Symbol 時丟出一個 TypeError,但我們確定丟進去的是一個 Number (F(k)),因此不會有丟出錯誤的可能。
ECMAScript 其實並沒有規定使用 map() 的物件必須是一個陣列,從步驟 1 跟 Note 2 可以看得出來,我們來驗證一下:

map() 應該是使用頻率最高的 Array method 之一,善用它可以大幅度增加寫 code 的效率及可讀性,另外還可以減少宣告額外變數的機會!
最後,希望大家可以開心地使用各種咩色,體驗它帶給你的便利,祝大家歸剛沒煩惱。