【JavaScript中級編】4章 モダンJavaScript

JavaScript

こんにちは。ECF Tech担当
Michiharu.Tです。

JavaScript中級編 第4回をお送りしたいと思います。今回から、モダンJavaScriptと呼ばれるようなJavaScriptの新機能について見ていきます。最近のフロントエンド開発には欠かせない知識となっています。ぜひ、日ごろの学習にお役立て頂ければと思います。

本連載の章立ての一覧については下記のリンクから確認できます。

初心者向け!JavaScriptプログラミングスタート講座
こんにちは、ECF Techブログ担当のMichiharu.Tです。本記事はプログラミングを始めてみたいという方のために作成された連載記事の目次ページです。実際にプログラムを作って動かしながら学べるWeb教材です。JavaScript初級編...

4-1 EcmaScript

EcmaScriptはJavaScriptの標準仕様です。ブラウザなどに搭載するJavaScriptはこのような言語仕様とし、このように動作させてね。という標準ルールです。現在のブラウザは基本的にこのEcmaScriptの仕様に基づいたJavaScriptが搭載されています。

EcmaScriptは2015年以降、EcmaScript XXXXの表記で毎年新バージョンを発行しています。中でも初回のEcmaScript 2015では大きな仕様追加が行われ、JavaScriptが大規模な開発やバックエンドにも用いられる言語に進化するきっかけとなりました。

このため、EcmaScript 2015以降のJavaScriptは「モダンJavaScript」と呼ばれるようになり、現在のJavaScript開発における標準的な書き方として広く採用されています。

本章以降は、EcmaScript 2015以降に追加された機能の中でも特に知っておきたいものについて、プログラム例と共に見ていきます。

4-2 オブジェクト指向プログラミング

追加機能の大きなポイントの1つに、オブジェクト指向プログラミングが可能な文法の追加があります。それまでもオブジェクト指向プログラミングは可能でしたが、「クラス」や「継承」などの概念を明確に取り入れたのはEcmaScript 2015からとなります。本節では基本的なオブジェクト指向プログラミングの文法をご紹介します。

4-2-2 クラス

オブジェクト指向プログラミングには、クラスという概念があります。クラスは簡単に言えば、オブジェクトの設計図です。その設計図を基に実際のオブジェクトを生成します。下にイメージを示します。

クラス定義のための基本的な書き方を示します。

class クラス名{
    constructor(引数...){
        //初期化処理
    }
    //以下メソッド定義
    methodA(引数...){
        //処理
    }
}

3章で学習したコンストラクタと大きな違いはありません。主なポイントは下記です。

  • constructorと名前のつくメソッドを作成し、プロパティの初期化を行います。
  • メソッド定義はfunctionの記述は不要です。

プログラム例を示します。

プログラム
//クラス定義
class Person{
    constructor(name, age){
        this.name = name;
        this.age = age;
    }
    introduce(){
        console.log(`${this.name}です。${this.age}歳です。`);
    }
}
//クラスからオブジェクトを生成
const taro = new Person('太郎',24);
taro.introduce();
実行結果
太郎です。24歳です。

人物を表すPersonクラスを作成しています。クラスからオブジェクトを生成する際はnewキーワードを使い、次のように記述します。

new クラス名(引数...);

この記述はオブジェクトを生成するためにコンストラクタを呼び出します。したがって、引数部分はコンストラクタに渡す引数となります。例では

const taro = new Person('太郎',24);

となっているため'太郎'24がそれぞれ引数として渡されます。よって、変数taroのオブジェクトはnameageのそれぞれのプロパティに'太郎'24が代入されています(下図)。

Personオブジェクト

生成されたオブジェクトはクラスで定義されたメソッドも持っています。メソッドの呼び出しはオブジェクトを指す変数を使って変数名.メソッド名(引数...)です。例ではtaro.introduce();の記述によって、introduceメソッドを実行しています。

4-2-3 継承

類似するクラスを作成したい場合には、継承を使うことで効率的にクラスを作成することができます。4-2-2で作成したPersonクラスに加え、従業員を表すEmployeeクラスを考えてみましょう。次のようなクラスが考えられます。

プログラム
class Employee{
    constructor(name,age,nendo,busyo){
        this.name = name;   //名前
        this.age = age;     //年齢
        this.nendo = nendo; //入社年度
        this.busyo = busyo; //所属部署
    }
    introduce(){
        console.log(`${this.name}です。${this.age}歳です。`);
        console.log(`所属部署は${this.busyo}です。`);
    }
}
const taro = new Employee('太郎',24,2020,'情報推進部');
taro.introduce();
実行結果
太郎です。24歳です。
所属部署は情報推進部です。

この作成方法でも問題は無いのですが、継承を使うことでより効率的なプログラムを書くことができます。継承とはあるクラスのプロパティやメソッドを引き継いで、別のクラスを作成する方法です。下図のようなイメージです。この時、継承される側のクラスを親クラス、継承する側のクラスを子クラスと言います。

継承

継承を使ってクラスを作成する際の基本的な文法は次のようになります。

class 子クラス名 extends 親クラス名{
    constructor(引数...){
        //親クラスのコンストラクタを呼び出す。
        super(引数..);
    }
    //以下、メソッド定義

}

同じ動作をするプログラムを継承のしくみを使って書き直した例が下になります。

プログラム
//人物クラス
class Person{
    constructor(name, age){
        this.name = name;
        this.age = age;
    }
    introduce(){
        console.log(`${this.name}です。${this.age}歳です。`);
    }
}
//従業員クラス
class Employee extends Person{
    constructor(name,age,nendo,busyo){
        super(name,age);
        this.nendo = nendo; //入社年度
        this.busyo = busyo; //所属部署
    }
    introduce(){
        super.introduce();
        console.log(`所属部署は${this.busyo}です。`);
    }
}
//オブジェクトの生成
const taro = new Employee('太郎',24,2020,'情報推進部');
taro.introduce();
実行結果
太郎です。24歳です。
所属部署は情報推進部です。

Employeeクラスの定義部にはextends Personがついており、Personクラスを継承して作成されていることがわかります。したがって、EmployeeクラスはPersonクラスが持っているプロパティやメソッドを引き継いでいます。

constructorは、name,age,nendo,busyoの4つの引数を受け取っています。そのうちnameとageは親クラスのコンストラクタにそのまま渡す記述となっています。super(name,age);がその部分です。親クラスのプロパティの初期化は、親クラスのコンストラクタに任せるのが基本的な書き方となります。下図はコンストラクタ呼び出し時の動作イメージ図です。図では見やすさのため、クラスごとに記述を分けています。

継承関係とコンストラクタ

また、Employeeクラスのintroduceメソッドでは、親クラスのintroduceメソッドを呼び出しています。super.introduce()がその部分です。これにより、「太郎です。24歳です。」の部分を表示しています。

4-3 スプレッド構文

スプレッド構文を使用すると、配列やオブジェクトの内容を展開できます。spread(広げるの意味)の名前のとおり、その場に値を広げるイメージです。配列やオブジェクトをコピーする際に便利です。

スプレッド構文の基本的な書き方は次のとおりです。

...配列名   //配列を展開する
...オブジェクト名   //オブジェクトを展開する

4-3-1 配列を展開する

始めにスプレッド構文で配列を展開してみましょう。プログラム例を示します。

プログラム例
const nums = [1,2,3];
function func1(x,y,z){
    console.log(`x=${x} y=${y} z=${z}`);
}
//(1)配列の一部として使用
console.log([...nums,4,5,6]);
//(2)配列を引数で渡す
func1(nums);
//(3)配列をスプレッド構文で渡す
func1(...nums);
実行結果
[ 1, 2, 3, 4, 5, 6 ]
x=1,2,3 y=undefined z=undefined
x=1 y=2 z=3

コメント(1)では、配列numを別の配列の一部として展開しています。実行結果では1~6の値が表示されていることがわかります。

コメント(2)では、関数func1にnumsを引数として渡しています。関数func1の引数は3つありますが、呼び出し側ではnumsとだけ記述しているため、最初の引数に配列が渡されるだけになります。したがって残り2つはundefinedが表示されています。

コメント(3)では、関数func1にnumsをスプレッド構文で渡しています。配列の内容が引数として展開され、引数x,y,zのすべてに値が入っていることがわかります。

4-3-2 オブジェクトを展開する

次にオブジェクトを展開する例を見てみましょう。まずはプログラム例を示します。

プログラム例
const taro = {
    name:'山田太郎',
    age:25,
};
//(1)スプレッド構文でコピー
const hanako = {...taro};
//(2)コピー内容を表示
console.log(hanako);
//(3)コピーオブジェクトを編集
hanako.name = '山田花子'
//両オブジェクトの確認
console.log(taro);
console.log(hanako);
実行結果
{ name: '山田太郎', age: 25 }
{ name: '山田太郎', age: 25 }
{ name: '山田花子', age: 25 }

コメント(1)部分でtaroオブジェクトをコピーし、変数hanakoに代入しています。次にコメント(2)の部分でコピーしたオブジェクトを表示しています。同じオブジェクトが作成できていることが確認できます。コメント(3)ではnameプロパティを変更しています。このように、スプレッド構文を使うことでオブジェクトをコピーすることができます。

一方で、スプレッド構文によるオブジェクトのコピーでは次の点に注意が必要です。プログラム例で確認します。

プログラム例
const taro = {
    name:{
        sei:'山田',
        mei:'太郎'
    },
    age:25,
};
//(1)スプレッド構文でコピー
const hanako = {...taro};
//(2)コピーオブジェクトを編集
hanako.age = 22;
hanako.name.mei = '花子'
//両オブジェクトの確認
console.log(taro);
console.log(hanako);
実行結果
{ name: { sei: '山田', mei: '花子' }, age: 25 }
{ name: { sei: '山田', mei: '花子' }, age: 22 }

コメント(1)部分でtaroオブジェクトをコピーし、変数hanakoに代入しています。次にコメント(2)の部分でコピーしたオブジェクトを花子固有の情報に変更しています。ですが、実行結果を見てみると変数taroの内容もmeiプロパティの値が'花子'になってしまっています。

これはスプレッド構文によるコピーが、プロパティがオブジェクトの場合にはそのオブジェクトを共有するタイプのコピーだからです。このようなコピー方法を シャローコピー(浅いコピー) と言います。今回のプログラムを例にシャローコピーを図示したものが下の図になります。

シャローコピー

上の図のように、このプログラムで意図していたオブジェクトのコピーは図の左側のものでしたが、実際には図の右側のようなコピーイメージになります。nameプロパティはオブジェクトであり、スプレッド構文ではコピーは行われません。実際には上図のように、taroとhanakoそれぞれのオブジェクトのnameプロパティが、共通のオブジェクトを指すかたちとなります。

そのため、hanako.name.meiでプロパティ値の変更がtaroオブジェクトにも影響を与えてしまったというわけです。

スプレッド構文によるオブジェクトのコピーは便利な反面、コピーの状態には注意して使う必要があります。

JavaScriptの変数は「値の入れ物」と言うよりは、「値を指し示すもの」という表現が正確です。

4-4 配列操作メソッド

ここでは配列を操作するための様々なメソッドをご紹介します。

4-4-1 Array.map

mapは配列の持つメソッドです。引数で与えられた関数を配列の各要素に対して呼び出し、その結果からなる新しい配列を返します。プログラム例を示します。

プログラム例
const array1 = [1, 2, 3, 4, 5];
const array2 = array1.map((x) => x * x);
console.log(array2);
実行結果
[ 1, 4, 9, 16, 25 ]

mapメソッドの引数にはアロー関数を渡しています。(x) => x * xは引数xを2乗する関数です。この引数xに配列の各要素が渡され処理されます。結果、元の配列の各要素を2乗した値を持つ配列が返されます。

4-4-2 Array.filter

filterは配列の持つメソッドです。引数で与えられた関数は真偽を判定する条件式を書きます。配列の各要素のうち、関数内の条件式がtrueとなるものだけが取り出され、新たな配列として返されます。プログラム例を示します。

プログラム例
const array1 = [2, 4, 7, 8, 9, 10];
const array2 = array1.filter((x) => x % 2 == 0);
console.log(array2);
実行結果
[ 2, 4, 8, 10 ]

filterメソッドに渡している関数は、引数の値が偶数か奇数かを判定するものです。偶数であれば条件式はtrueとなります。filterメソッド実行後の新しい配列からは奇数が除外されていることがわかります。

タイトルとURLをコピーしました