進行状況の追跡を始めよう
Trailhead のホーム
Trailhead のホーム

オブジェクト、クラス、プロトタイプの継承の操作

学習の目的

この単元を完了すると、次のことができるようになります。

  • オブジェクトリテラル表記とコンストラクタを使用してオブジェクトを作成する。
  • オブジェクトにプロパティと関数を代入する。
  • JavaScript のオブジェクト継承におけるプロトタイプの役割を特定する。
  • JavaScript のクラス構文について説明する。
  • Lightning Web コンポーネントにおける継承とオブジェクトリテラル表記の役割を説明する。

JavaScript という言語を説明する方法はたくさんあります。どのような定義を選ぶにしろ、JavaScript でオブジェクトの概念が重要であることには誰もが同意することでしょう。JavaScript のオブジェクトとそのしくみに対する理解が深まるほど、効率的な JavaScript を記述できるようになります。

始める前に、オブジェクトについていくつかの注意事項があります。 

  • オブジェクトには、Apex、Java、C# の開発者が考えるようなクラスがありません。
  • どのオブジェクトも別のオブジェクトから継承します。
  • オブジェクトは変更可能です。
  • オブジェクトは作成時に独自の可変コンテキストを取得します。

オブジェクトの作成

構文の点から言えば、JavaScript ではオブジェクトを数通りの方法で作成できます。けれども、オブジェクトをどのような方法で作成するにしろ、実際には Object.create() という基盤となる API を抽象化することになります。

場合によっては Object.create() を直接使用することが妥当なこともありますが、ここでは説明しません。その代わり、オブジェクトを作成するより一般的な方法を見ていきます。 

オブジェクトリテラル表記

1 つ目のオブジェクト作成構文は、オブジェクトリテラル表記というものです。これは、オブジェクトを一度にまとめて宣言して代入する、シンプルな宣言型の方法です。オブジェクトはその後、同じステートメントの一部としてただちに代入されます。 

const bike = {
  gears: 10,
  currentGear: 3,
  changeGear: function(direction, changeBy) {
    if (direction === 'up') {
      this.currentGear += changeBy;
    } else {
      this.currentGear -= changeBy;
    }
  }
}
console.log(bike.gears); // 10
console.log(bike.currentGear); //3
bike.changeGear('up', 1);
console.log(bike.currentGear); //4

オブジェクトリテラル表記は本質的に宣言型です。このbike オブジェクトには、gears プロパティと currentGear プロパティと changeGear 関数という 3 つのメンバーがあります。オブジェクトが作成された後でこれらのメンバーを参照する場合は、ドット表記を使用します。 

メモ

メモ

JSON とオブジェクトリテラル表記が互いによく似ていることに気付いたかもしれませんが、同じではありません。JSON はデータ交換形式です。オブジェクトリテラル表記はプログラミング構文です。ただし、JSON 仕様はオブジェクトリテラル表記に基づくため、両者を混同しがちです。

リテラルオブジェクトは単発のオブジェクトに適しています。他方、同じ種別のオブジェクトを 2 つ以上作成する場合は、実用的ではありません。この場合は、新しいオブジェクトを作成する、繰り返し可能なロジックが必要です。 

コンストラクタを使用した新しいオブジェクト

オブジェクトを作成するもう 1 つの方法はコンストラクタを使用することです。コンストラクタとは、オブジェクトを作成して代入するときにそのプロパティを設定するための説明を含む関数です。この方法では、同じプロパティのオブジェクトのインスタンスを多数作成できるという点で、オブジェクトリテラルよりも便利です。 

function Bike(gears, startGear) {
  this.gears = gears;
  this.currentGear = startGear;
}
Bike.prototype.changeGear = function(direction,changeBy){
  if(direction === 'up') {
    this.currentGear += changeBy;
  } else {
    this.currentGear -= changeBy;
  }
}
const bike = new Bike(10, 3);
console.log(bike.gears); // 10
console.log(bike.currentGear); //3
bike.changeGear('up', 1);
console.log(bike.currentGear); //4

このBike は、オブジェクトを定義する通常の JavaScript 関数です。ここでは JavaScript のルールに従って、この関数がコンストラクタであることを知らせるために最初の文字を大文字にしています。この new キーワードは極めて重要です。new がなければ、this ポインタが意図するオブジェクトをポイントせず、予期しない動作が生じます。this ついては、後の単元のコンテキストの説明でもう一度取り上げます。 

changeGear 関数の代入は、prototype というものを使用して行われます。この方法では、関数が一度定義されると、このコンストラクタで作成されたすべてのインスタンスに確実に共有されます。プロトタイプの使用と継承については、この単元の後半で説明します。 

構文の点では、オブジェクトリテラル表記とコンストラクタはかなり異なりますが、どちらの場合も、新しいオブジェクトがメモリに作成され、bike 変数がそのオブジェクトへのポインタになります。コンストラクタを使用すると、同じプロパティと関数が設定された Bike オブジェクトをたくさん作成できます。

オブジェクトへのプロパティと関数の代入

上記の bike の例から、オブジェクトにはプロパティと関数という 2 種類のメンバーがあるのではないかと推測した方、正解です。

プロパティには次の 3 つの基本的な形態があります。

  • プリミティブ
  • オブジェクト
  • 配列

JavaScript には、文字列、数値、Boolean、nullundefined、シンボルの 6 つのプリミティブ型があります。変数がプリミティブ型の場合は、代入時に値によって変数が渡されます。つまり、プリミティブが代入されるたびに、値のコピーが作成され、新しい変数に代入されます。 

JavaScript でプリミティブに該当しないものはほぼすべてオブジェクトです。オブジェクトリテラル表記では、オブジェクトのプロパティが中括弧で囲まれます。 

配列自体も JavaScript ではオブジェクトとして実装されます。配列は、Array() コンストラクタ関数、または角括弧で囲まれたリテラル表記を使用して作成できます。 

関数については、このモジュールに独自の単元があるため、ここでは説明しませんが、上記を基にもう一度オブジェクトリテラル表記を使用してさらに複雑な bike オブジェクトを定義してみましょう。

const bike = {
  frontGearIndex: 0,
  rearGearIndex: 0,
  transmission: {
    frontGearTeeth: [30,45],
    rearGearTeeth: [11,13,15,17,19,21,24,28,32,36]
  },
  calculateGearRatio: function() {
    let front = this.transmission.frontGearTeeth[this.frontGearIndex],
        rear = this.transmission.rearGearTeeth[this.rearGearIndex];
    return (front / rear);
  },
  changeGear: function(frontOrRear, newValue) {
    if (frontOrRear === 'front') {
      this.frontGearIndex = newValue;
    } else {
      this.rearGearIndex = newValue;
    }
  }
};

括弧構文によるプロパティの参照

オブジェクトメンバーの参照は、通常ドット表記を使用して行います。たとえば、前の例では、オブジェクトのプロパティと関数を次のように参照します。

bike.frontGearIndex
bike.transmission.frontGearTeeth
bike.calculateGearRatio()

ドット表記では、プロパティの名前に厳密なルールがあります。けれども JavaScript では、括弧表記という別の構文も使用できます。括弧表記では上記のメンバーが次のように参照されます。

bike["frontGearIndex"]
bike["transmission"]["frontGearTeeth"]
bike["calculateGearRatio"]()

括弧表記のほうが入力する文字数が多いものの、2 つの利点があります。プロパティや関数に任意の名前を付けることができます。そして、文字列であるため、変数を介してプロパティや関数の名前を渡してコールできます。

では、changeGear 関数を再考し、この方法ではどのように機能するのか見てみましょう。ここでは、フロントギアとリアギアの上下のシフトを定義する 4 つの関数を使用します。changeGear 関数で、文字列パラメータに基づいてコールする関数の名前を構築し、その名前をコールします。 

changeGear: function(frontOrRear, upOrDown) {
  let shiftFunction = frontOrRear + upOrDown;
  this[shiftFunction]();
},
frontUp: function(){
  this.frontGearIndex += 1;
},
frontDown: function(){
  this.frontGearIndex -= 1;
},
rearUp: function(){
  this.rearGearIndex += 1;
},
rearDown: function(){
  this.rearGearIndex -= 1;
}

これらの名前を bike オブジェクトに追加すると、どのように機能するのかを確認できます。 

console.log(bike.calculateGearRatio()); // 2.727272727
//Calls the frontUp() function
bike.changeGear("front", "Up");
console.log(bike.calculateGearRatio()); // 4.090909091
//calls the rearUp() function
bike.changeGear("rear", "Up");
console.log(bike.calculateGearRatio()); // 3.461538461

オブジェクトの変更可能性

オブジェクトを定義する各種の構文のほか、JavaScript のオブジェクトにはもう 1 つ重要な原則があります。変更可能性です。 

JavaScript のオブジェクトは変更可能で、オブジェクトの形態を変えたければ変更できることを意味します。 

作成した bike オブジェクトを見てみましょう。たとえば、新しいプロパティや関数を追加することができます。 

bike.isTandem = true;
bike.popAWheelie = function() {
…
};

オブジェクトが最初に定義されたコードにアクセスできなくても、オブジェクトがメモリに入れられた後でその形態を変更できます。ここで重要な点は、オブジェクトの 1 つのインスタンスのみが変更されることです。では、もう一度 Bike コンストラクタを見てみましょう。

const bike1 = new Bike();
const bike2 = new Bike();
bike1.isTandem = true;
console.log(bike1.isTandem); // true
console.log(bike2.isTandem); // undefined

いくつかのオブジェクトでプロパティまたはメソッドを共有したい場合は、継承モデルを使用します。詳しく見てみましょう。 

オブジェクトと継承

JavaScript には従来の言語で定義されていたようなクラスはありませんが、プロトタイプ継承という継承モデルがあります。 

プロトタイプは、実際のところもう 1 つのオブジェクトです。これはメモリ内に存在し、他のオブジェクトが同じプロトタイプを共有する場合に継承するプロパティや関数を定義します。 

JavaScript では従来、オブジェクトが同じコンストラクタ関数を共有するというやり方で、同じプロトタイプを共有します。Bike コンストラクタを思い出してください。changeGear 関数を prototype というものに代入します。 

function Bike(gears, startGear) {
  this.gears = gears;
  this.currentGear = startGear;
}
Bike.prototype.changeGear = function(direction, changeBy) {
  if (direction === 'up') {
    this.currentGear += changeBy;
  } else {
    this.currentGear -= changeBy;
  }
}

こうすれば、Bike から作成される各オブジェクトが changeGear 関数を継承します。 

プロトタイプには、複数レベルの継承を実装でき、プロトタイプチェーンとして参照されます。コンストラクタ関数を使用したプロトタイプチェーンの実装は複雑で、相当量の定型コードが必要です。この点も当モジュールの範疇を超えています。ここで知っておくべきことは、プロトタイプチェーンの複雑さに対処するために、ECMA が、継承を実装するより端的な構文、つまり class 構文に関する標準を導入しているということです。 

クラスと JavaScript

「クラス」という単語を読んで安堵感を覚え、真のクラスベースの継承を作成するものを見ていくのかなと思った方は、落胆することになるでしょう。JavaScript の class というキーワードは、コンストラクタ関数を使用してプロトタイプ継承の複雑性に対処する糖衣構文のようなものです。上辺の糖衣の下では、この場合もエンジンが Object.create を使用し、(オブジェクト指向という意味での) クラスはありません。メモリ内にプロトタイプオブジェクトがあるだけで、実際はこれが継承元になります。 

幸いにも、JavaScript 固有の事項をいくつか考慮する必要はあるものの、見た目は Java や C# のコードによく似ています。 

メモ

メモ

Lightning Web コンポーネントで class 構文を含む複数レベルのプロトタイプチェーンを使用しますが、このモジュールの説明の範疇を超えています。JavaScript オブジェクトの最新機能の操作についての詳細は、この単元の末尾の「リソース」にあるリンクを参照してください。このトレイルの「Modern JavaScript Development (JavaScript の最新機能)」という別のモジュールを受講することを強くお勧めします。同モジュールでは 1 つの単元を丸々クラスの説明に費やしています。

ここでは JavaScript クラスについて詳述しませんが、後学のために class 構文を使用して bike オブジェクトを実装したバージョンを確認しておくとよいでしょう。 

class Bike {
    constructor(gears, startGear){
        this.gears = gears;
        this.currentGear = startGear;
    }
    changeGear(direction, changeBy) {
        if (direction === 'up') {
            this.currentGear += changeBy;
        } else {
            this.currentGear -= changeBy;
        }
    }
}
const bike = new Bike(10, 5);
console.log(bike.currentGear); // 5
bike.changeGear('up', 2);
console.log(bike.currentGear); // 7

ご覧のとおり、この構文は Java や Apex のクラスとよく似ています。明確な違いは、コンストラクタ関数に常に constructor という名前が付けられていることです。重要な特徴は、Object.prototype を直接参照しなくても、関数や属性が自動的にプロトタイプチェーンに属することです。このため、複数レベルのプロトタイプ継承を簡単に作成できます。 

Lightning Web コンポーネントとオブジェクト

これまでに説明した数種の構文やプロトタイプチェーンなど、この単元の内容のいくつかは Lightning Web コンポーネントの開発に関連しています。 

クラスと Lightning Web コンポーネント

Lightning Web コンポーネントは、JavaScript の多数の最新機能を活用しており、その代表例が class 構文の使用です。コンポーネントは通常、LightningElement という別のクラスを拡張する JavaScript クラスによって定義されます。次のようになります。 

import { LightningElement } from lwc;
export default class MyComponent extends LightningElement {
    myProperty;
    myFunction() {
        console.log(this.myProperty);
    }
} 

Lightning Web コンポーネントの機能は、JavaScript クラスで定義されます。この例では、モジュール (importexport) に関してまだ説明していない一部の構文も使用しています。

オブジェクトリテラル

このモジュールのいくつかの例では、オブジェクトがどのように機能するかを学習する目的で、オブジェクトリテラル内で関数を宣言しています。このやり方は、最新の JavaScript で推奨される実践法ではありません。オブジェクトリテラルは、JavaScript プログラムの機能部分間でデータを渡すアドホックデータ構造を作成する優れた方法ですが、オブジェクトリテラルで関数を定義することは回避します。 

リソース

JavaScript の Object

オブジェクトの利用

JavaScript のオブジェクトモデルの詳細

Trailhead プロジェクト: Lightning Web コンポーネントを使用した熊追跡アプリケーションの作成

Lightning Web Components Developer Guide (Lightning Web コンポーネント開発者ガイド): Define a Component (コンポーネントの定義)