Array.map() 四種實用作法

重點:
ㄧ、整個 Array 裡頭個別 item 做變化
二、把 string 轉成 Array
三、Reformatting Array Object
四、Combine .map(), .reduce(), and .filter() & forEach 有時很難用(不會 return)

ㄧ、整個 Array 裡頭個別 item 做變化:

const oldArray = [2, 3, 4, 5];
const newArray = oldArray.map(x => {
    return x * 2
})

console.log(newArray) // [4, 6, 8, 10]


再簡化一點的寫法 → 直接寫一個 function 包在 map 裡:

// create a function to use
const newArray = x => x * 2;

// we have an array 
const oldArray = [2, 3, 4, 5];

// call the function we made. more readable
const a = oldArray.map(newArray);

console.log(a); // [4, 6, 8, 10];

二、把 string 轉成 Array :用 call()

.call() can be passed arguments of, the context to be used, and “parameters for the arguments of the original function”

這個方法滿像 String​.prototype​.split() ,把字串轉成 Array,只是這個方法可以把個別單字做變化。

const name = "Annie";
const map = Array.prototype.map;
//把 name 上面的每一個 paramater 都加上 a
const newName = map.call(name, eachLetter => { return `${eachLetter}a` })
console.log(newName) // ["Aa", "na", "na", "ia", "ea"]

三、Reformatting Array Objects : handling objects in an Array

const myUsers = [
 { name: ‘chuloo’, likes: ‘grilled chicken’ },
 { name: ‘chris’, likes: ‘cold beer’ },
 { name: ‘sam’, likes: ‘fish biscuits’ }
]

const usersByFood = myUsers.map(item => {
const container = {};
container[item.name] = item.likes;
container.age = item.name.length * 10;
return container;
})

console.log(usersByFood);
// [{chuloo: “grilled chicken”, age: 60}, {chris: “cold beer”, age: 50}, {sam: “fish biscuits”, age: 30}]

四、Combine .map(), .reduce(), and .filter()

先比較 map 跟 forEach 寫法:

// What you have
var officers = [
 { id: 20, name: ‘Captain Piett’ },
 { id: 24, name: ‘General Veers’ },
 { id: 56, name: ‘Admiral Ozzel’ },
 { id: 88, name: ‘Commander Jerjerrod’ }
];
//forEach 的方式
var officersIds = [];
officers.forEach(function (officer) {
 officersIds.push(officer.id);
});

console.log(officersIds); //[20, 24, 56, 88]

// What you have
var officers = [
{ id: 20, name: ‘Captain Piett’ },
{ id: 24, name: ‘General Veers’ },
{ id: 56, name: ‘Admiral Ozzel’ },
{ id: 88, name: ‘Commander Jerjerrod’ }
];

// What you need[20, 24, 56, 88]

//forEach 的方式
var officersIds = [];
officers.forEach(function (officer) {
officersIds.push(officer.id);
});

console.log(officersIds); //[20, 24, 56, 88]

//map 的方式
var officersIds = officers.map(function (officer){
return officer.id
});

console.log(officersIds);

// map 更精簡寫法
var officersIds = officers.map(officer => officer.id);
console.log(officersIds);

map():

*Basically is takes 2 arguments, a callback and an optional context (will be considered as this in the callback) .

*The callback runs for each value in the array and returns each new value in the resulting array.

*Keep in mind that the resulting array will always be the same length as the original array.

.reduce()

Just like .map().reduce() also runs a callback for each element of an array. What’s different here is that reduce passes the result of this callback (the accumulator) from one array element to the other.

The accumulator can be pretty much anything (integer, string, object, etc.)

var pilots = [
  {
    id: 10,
    name: "Poe Dameron",
    years: 14,
  },
  {
    id: 2,
    name: "Temmin 'Snap' Wexley",
    years: 30,
  },
  {
    id: 41,
    name: "Tallissan Lintra",
    years: 16,
  },
  {
    id: 99,
    name: "Ello Asty",
    years: 22,
  }
];

假設要找到所有 pilot 的年齡總數:

var totalYears = pilots.reduce(function (accumulator, pilot) {
  return accumulator + pilot.years;
}, 0);

// 82
//這裡是從 0 開始算

ES6 簡潔寫法:

const totalYears = pilots.reduce((acc, pilot) => acc + pilot.years, 0);

假設要找到誰最有經驗:

var mostExpPilot = pilots.reduce(function (oldest, pilot) {
  return (oldest.years || 0) > pilot.years ? oldest : pilot;
}, {});
//Object { id: 2, name: "Temmin 'Snap' Wexley", years: 30 }

I named my accumulator oldest. My callback compares the accumulator to each pilot. If a pilot has more years of experience than oldest, then that pilot becomes the new oldest so that’s the one I return.

As you can see, using .reduce() is an easy way to generate a single value or object from an array.

.filter()

What if you have an array, but only want some of the elements in it.

var pilots = [
  {
    id: 2,
    name: "Wedge Antilles",
    faction: "Rebels",
  },
  {
    id: 8,
    name: "Ciena Ree",
    faction: "Empire",
  },
  {
    id: 40,
    name: "Iden Versio",
    faction: "Empire",
  },
  {
    id: 66,
    name: "Thane Kyrell",
    faction: "Rebels",
  }
];

//要找 faction 是 Rebels 或 Empire 的
var rebels = pilots.filter(function (pilot) {
 return pilot.faction === "Rebels";
});

var empire = pilots.filter(function (pilot) {
 return pilot.faction === "Empire";
});

//ES6 寫法
const rebels = pilots.filter(pilot => pilot.faction === "Rebels");
const empire = pilots.filter(pilot => pilot.faction === "Empire");

//得到的解答
Array [Object { id: 2, name: "Wedge Antilles", faction: "Rebels" }, Object { id: 66, name: "Thane Kyrell", faction: "Rebels" }]
Array [Object { id: 8, name: "Ciena Ree", faction: "Empire" }, Object { id: 40, name: "Iden Versio", faction: "Empire" }]

Basically, if the callback function returns true, the current element will be in the resulting array. If it returns false, it won’t be.


Combine .map(), .reduce(), and .filter()

var personnel = [
  {
    id: 5,
    name: "Luke Skywalker",
    pilotingScore: 98,
    shootingScore: 56,
    isForceUser: true,
  },
  {
    id: 82,
    name: "Sabine Wren",
    pilotingScore: 73,
    shootingScore: 99,
    isForceUser: false,
  },
  {
    id: 22,
    name: "Zeb Orellios",
    pilotingScore: 20,
    shootingScore: 59,
    isForceUser: false,
  },
  {
    id: 15,
    name: "Ezra Bridger",
    pilotingScore: 43,
    shootingScore: 67,
    isForceUser: true,
  },
  {
    id: 11,
    name: "Caleb Dume",
    pilotingScore: 71,
    shootingScore: 85,
    isForceUser: true,
  },
];

目標: get the total score of force users only.

//filter 出 forcreUser
var jediPersonnel = personnel.filter(function (person) {
  return person.isForceUser;
});
// Result: [{...}, {...}, {...}] (Luke, Ezra and Caleb)
//create an array containing the total score of each Jedi
var jediScores = jediPersonnel.map(function (jedi) {
  return jedi.pilotingScore + jedi.shootingScore;
});

// Result: [154, 110, 156]
// 用 reduce 來取得總數
var totalJediScore = jediScores.reduce(function (acc, score) {
 return acc + score;
}, 0);
// Result: 420
---
//完整寫法:
var totalJediScore = personnel
.filter(function (person) {
 return person.isForceUser;
})

.map(function (jedi) {
return jedi.pilotingScore + jedi.shootingScore;
})

.reduce(function (acc, score) {
return acc + score;
}, 0);
//ES6 簡潔寫法:
const totalJediScore = personnel
  .filter(person => person.isForceUser)
  .map(jedi => jedi.pilotingScore + jedi.shootingScore)
  .reduce((acc, score) => acc + score, 0);

Why not use .forEach( )?

source:https://medium.com/poka-techblog/simplify-your-javascript-use-map-reduce-and-filter-bd02c593cc2d

var data = [
  {
    name: "Jan Dodonna",
    title: "General",
  },
  {
    name: "Gial Ackbar",
    title: "Admiral",
  },
]

The API gives you the above data, but you only need the title and the last name of each person… You need to format the data. However, your app also needs to have a single view for each person, so you must write a data formatting function that both works in a list view and in a single view.

That means you can’t have the .forEach loop inside of your formatting function, or else you would have to wrap your single element in an array before you pass it to the function just to make it work, like so:

var result = formatElement([element])[0];
// Yeah... that's not right at all

So your loop has to wrap the call of the function, like this:

data.forEach(function (element) {
  var formatted = formatElement(element);
  // But what then....
});

But .forEach() doesn’t return anything. It can’t. That means you have to push the results inside a predetermined array.

var results = [];
data.forEach(function (element) {
  var formatted = formatElement(element);
  results.push(formatted);
});

As a result, you have 2 functions: your formatElement() function and your function that pushes the results in your array.

Why have 2 functions when you can have just one?

var results = data.map(formatElement);