2021年9月にSIerからWebサービス企業に転職をして、これまであまり触れてこなかったフロントエンド開発をするようになりました。(前職では主にバックエンドだったので)
しかも、モダンなTypeScriptを使っているということでキャッチアップしなければいけなかったんですが、何がTypeScriptの機能で何がJavaScriptの機能なのか、あまり理解できなかったんですよね・・
TypeScriptは結局JavaScriptがベースの言語なので、まずはJavaScriptが理解できていないことには始まらないと思い、JavaScriptから勉強を始めることにしました。
前職では少しだけJavaScriptを触ったことがありましたが、ES6というモダンなJavaScriptは触ったことがなく、これからはES6以降を理解していないとダメだなと感じたので、ES6の勉強をするに至りました。
フロントエンドエンジニアを志望している方、現在フロントエンドエンジニアだがES6を理解していない方は是非一読下さい!!
この記事で分かること
- ES6とは?
- ES6で実装された配列の便利メソッドの使い方
ES6とは
略さず言うと、ECMAScript6になります。
そもそもECMAScriptってなんですか?というところだと思いますが、
お堅くいうと”標準規格”、ザックリ言うと”バージョン”と理解してしまっていいと思います。
(Java8とかJava11でいうところの8,11に当たるもの)
JavaScript自体にはバージョンという概念がないため、ES5だとかES6というバージョン(標準規格)で語られたりしますので覚えておくと良いでしょう!
たまにES6ではなく、ES2015と言う方もいると思いますが、同じことです。
ES6というバージョンが2015年に出たこともあり、ES2015と言う派閥も一定数いるということですね。(ES7=ES2016など)
ES6はモダンなJavaScriptと言われており、ES5まではなかったClassが出たり、配列を簡単に操作できる便利メソッドが出たりと大変革が起きた?バージョンになります。
JavaでいうとStreamAPIが出たJava8あたりの温度感なのかな・・?
配列の便利メソッド
この記事ではES6の機能のなかでも、配列の便利メソッドに焦点を絞って説明したいと思います!
- forEach
- map
- filter
- find
- every
- some
- reduce
ES6で便利メソッドが実装されたことで、ES5までのコードに比べて、どう簡単に記述できるようになったのかを見ていきたいと思います。
BeforeがES5までのコード、AfterがES6でのコードを指します。
このAfterの例で、ES6で出てきた他の機能(アロー関数や,const, オブジェクトリテラル)も含めていますが、そこについては追々記事にしていきたいと思います!
forEach
forEach()メソッドは、与えられた関数(本サンプルでいうとconsole.log())を、配列の各要素に対して一度ずつ実行します。
例えば、以下のように社員情報を持つ配列があるとします。その社員情報を、一人一人ログに出力するというプログラムをサンプルに取ります。
var employees = [
{ id: 1, name: "鈴木" },
{ id: 2, name: "佐藤" },
{ id: 3, name: "高橋" }
];
function handleEmployees(employees) {
for (var i = 0; i < employees.length; i++) {
console.log(employees[i]);
}
}
handleEmployees(employees);
function handleEmployees(employees) {
employees.forEach(employee => {
console.log(employee);
});
}
handleEmployees(employees);
{id: 1, name: "鈴木"}
{id: 2, name: "佐藤"}
{id: 3, name: "高橋"}
map
map()メソッドは、与えられた関数を配列のすべての要素に対して呼び出し、その結果からなる新しい配列を生成します。
例えば、以下のように三角形の配列があるとします。
底辺と高さの情報を持つ三角形が3つ存在しており、それぞれの三角形の面積を求めて、新しく配列を作り、値を格納したいとします。
// 三角形の配列
var triangles = [
{ id: 1, height: 34, bottom: 10 },
{ id: 2, height: 90, bottom: 50 },
{ id: 2, height: 59, bottom: 25 }
];
// 各三角形の面積
var areas = [];
for (var i = 0; i < triangles.length; i++) {
var object = {
id: triangles[i].id,
area: triangles[i].height * triangles[i].bottom
};
areas.push(object);
}
console.log(areas);
// 各三角形の面積
const areas = triangles.map(triangle => {
return {
id: triangle.id,
area: triangle.height * triangle.bottom
}
});
console.log(areas);
もはやpush()も必要ないんですね・・・笑
trianglesの要素分だけ
(3) [Object, Object, Object]
0: Object
id: 1
area: 340
1: Object
id: 2
area: 4500
2: Object
id: 2
area: 1475
filter
filter() メソッドは、与えられた条件に合致する要素からなる新しい配列を生成します。
例えば以下のように、ユーザの配列があるとします。それぞれadmin権限の有無をプロパティに持っています。
この配列から、admin権限を有する(adminがtrue)ユーザだけを抽出して、新しい配列を作りたいとします。
var users = [
{ id: 1, admin: true },
{ id: 2, admin: false },
{ id: 3, admin: false },
{ id: 4, admin: false },
{ id: 5, admin: true },
];
var filteredUsers = [];
for (var i = 0; i < users.length; i++) {
if (users[i].admin === true) {
filteredUsers.push(users[i]);
}
}
console.log(filteredUsers);
const filteredUsers = users.filter(user => {
return user.admin === true;
})
console.log(filteredUsers);
if文が消えました!コードの可読性が高まりますよね。
user.admin === trueに合致する要素だけをfilteredUsersに格納していくイメージです。
(2) [Object, Object]
0: Object
id: 1
area: true
1: Object
id: 5
area: true
find
find()メソッドは、与えられた条件に合致する最初の要素の値を返します。
若干filter()と似ていますが、違いは該当する要素を1つ返したら処理を終了するかどうかという点です。
例えば先の例で、admin権限を有するユーザを一人だけ探したいという場合は以下のようになります。
var users = [
{ id: 1, admin: true },
{ id: 2, admin: false },
{ id: 3, admin: false },
{ id: 4, admin: false },
{ id: 5, admin: true },
];
var user;
for (var i = 0; i < users.length; i++) {
if (users[i].admin === true) {
user = users[i];
break;
}
}
console.log(user);
先の例と違うのは、一人だけを探せればいいので、一人見つかったらループを回す必要がなくなるということ。
ですので、一人見つかった時点でbreakをしてループを抜けるようにしています。
const user = users.find(function(user){
return user.admin === true;
})
console.log(user);
こちらも非常にスッキリコードが書けます!breakとか特に気にせずに良いので楽ですね♪
{id: 1, admin: true}
id: 1
admin: true
every
every() メソッドは、配列の要素全てが与えられた条件に合致するかどうかを検査します。論理値(true/false)を返します。
全ての要素が条件に合致すればtrueを返却し、条件に合致しない要素が一つでもあれば、falseが返却されます。
いわゆる論理積というやつですね。
例えば、すべての人がアンケートに回答してくれたかどうかを判定したい場合があるとしましょう。すべての人がアンケートに回答している場合のみtrueを返却するとします。(アンケート回答済みはansweredがtrueの場合です)
var users = [
{ year: 21, answered: true },
{ year: 62, answered: false },
{ year: 10, answered: true }
];
var isAllAnswered = true;
for (var i = 0; i < users.length; i++) {
if (users[i].answered === false) {
isAllAnswered = false;
break;
}
}
console.log(isAllAnswered);
全ての人がアンケート回答済かどうかが判定された変数(isAllAnswered)を用意して、最初にtrueを設定しておきます。
一人でもfalseがいればfalseにすべきなので、if文でアンケート回答済でない人がいるかどうかをチェックします。いればisAllAnsweredをfalseに反転するというロジックですね。
あと、一人でもfalseがいれば、あとはどう足掻いてもfalseなので、ループを回す必要がないということでbreakでループを抜けるようにします。
今は簡単なロジックですが、条件が複雑になったり、コード自体が長くなるとisAllAnsweredにどっちを入れるべきか混乱してくることがあるんですよね・・
const isAllAnswered = users.every(user => {
return user.answered === true;
})
console.log(isAllAnswered);
どうですか・・めちゃくちゃスッキリしましたよね。Beforeのように、isAllAnsweredを反転したり、初期値をどちらにすべきかとかを悩む必要がないので間違いも確実に減ります!
false
some
some()はevery()と似ていますね!
違いとしては与えられた条件に対して全ての要素をチェックし、一つでの合致している要素があればtrueを返却します。
全ての要素が条件に一致していない場合のみfalseを返却します。
いわゆる論理和ですね。
先と同じ例で見てみましょう。アンケート回答済が一人でもいるかを知りたいケースがあるとします。
var users = [
{ year: 21, answered: true },
{ year: 62, answered: false },
{ year: 10, answered: true }
];
先の例とは初期値と判定文の条件が逆ですね。
var isAllAnswered = false;
for (var i = 0; i < users.length; i++) {
if (users[i].answered === true) {
isAllAnswered = true;
break;
}
}
console.log(isAllAnswered);
const isAllAnswered = users.some(user => {
return user.answered === true;
});
console.log(isAllAnswered);
true
reduce
配列の便利メソッドで一番難しいのが、このreduce()でしょう。
reduce()メソッドは、配列のそれぞれの要素に対して、順次定義した関数を呼び出します。その際、直前の要素における計算結果の返値を渡します。配列のすべての要素に対して縮小関数を実行した結果が単一の値が最終結果になります。
説明だけだとちょっと分かりづらいと思いますので具体例で見ていきます。
例えば、ある複数の人のデータを持つ配列から、男女がそれぞれ何人いるかを知りたいケースがあるとします。
var persons = [
{ year: 10, gender: "man" },
{ year: 21, gender: "woman" },
{ year: 25, gender: "woman" },
{ year: 19, gender: "woman" },
{ year: 29, gender: "man" }
];
var man = 0;
var woman = 0;
for (var i = 0; i < persons.length; i++) {
if (persons[i].gender === "man") {
man += 1;
}
if (persons[i].gender === "woman") {
woman += 1;
}
}
var aggregateGender = { man, woman }; // オブジェクトリテラル
console.log(aggregateGender);
最初に男性と女性の数を集計する変数(man, woman)が必要です。
ループ内で条件に合った方の集計用変数をインクリメントしていくという流れですね。
const aggregateGender = persons.reduce((result, person) => {
if (person.gender === "man") result.man += 1;
if (person.gender === "woman") result.woman += 1;
return result;
}, { man: 0, woman: 0 });
console.log(aggregateGender);
これまでの便利メソッドでは、引数がコールバック関数だけでした。
しかし、reduce()では引数が2つ必要になる場合があります。第一引数がコールバック関数で、第二引数がコールバック関数内で使う累積値の初期値です。
以下の例でいうと、第二引数は{ man: 0, woman: 0 }
です。
ここで、更にややこしいことにコールバック関数の引数も2つ必要になります。
第一引数が累積値(途中結果を格納する変数)、第二引数が配列から取り出した要素です。
この累積値の初期値となるのが、先ほどの{ man: 0, woman: 0 }
になります。
つまり、配列の要素を順次取り出して男性か女性かを判定し、条件に合致する方の値をインクリメントします。その途中結果を次の処理に引き継いで(return result;で次の配列要素の処理へ途中結果を引き継いでいる)、配列の最終要素の処理が終わったときに最終的な集計値が出されているといった感じです。
{man: 2, woman: 3}
最後に
今回はES6で実装された配列の便利メソッドに焦点を絞って説明してきました。
他の機能(アロー関数や,const, オブジェクトリテラル)も含めていますが、そこについては追々記事にしていきたいと思います。
ES6の勉強を始めたら、ES6の便利さを感じれたとともに、業務でも「あ~、この記述はJavaScript(ES6)でもあった機能で、ここはTypeScriptにしかない機能なのね」と何となく理解できるようになってきました!
勉強の順番としてはJavaScript→TypeScriptの流れが正解だったと思います。