Objects (Part3) - creating multiple objects

June 29, 2017


Classes: definition

Introduction: the concept of “class” in object oriented programming languages

いままでシングルトン・オブジェクト:一度だけ生成されるオブジェクト:player, darkVador, etc だけを使ってきました。

このことは、少し違っています、ゲームの中でたくさんのボールを作ったことを忘れていました。 この例にすこし戻りましょう。

このボールを扱うのに、同じプロパティと同じメソッドを共有するが、プロパティの値は異なっていい 複数のオブジェクトを簡単に作成する方法としてテンプレートを使っていませんでした。

例えば、Luke Skywalker, Ian Solo and Dark Vadorを想像してください。 それらに共通なのはなですか。みなスターウォーズのヒーローで、名前があり、どちらかの味方(良い/悪い人々 つまり、反乱軍 vs 帝国軍)などなど。 オブジェクトそれ自体を記述するのではなく、オブジェクトのための”モデル”、”テンプレート”を記述する プログラミング手法があると創造してください。 それをStarWarsHeroとしましょう、これを使って ヒーローオブジェクトを生成します。

ゲームの複数のボールを思い出してください:みな同じ形(円)をしています、 同じプロパティの x, y, radius, color を持っていますが、 それらの値は異なっています。 みな同じオブジェクト(ball)クラスに所属していますが、プロパティの値という点では、みな異なっていました。

多くのプログラミング言語でこのテンプレートを"クラス"と呼びます

  • JavaScript 5 (ES5)では、このようなコンセプトはありません、その代わりに "constructor 関数"があります。
  • JavaScript 6 (ES6)では、クラスの概念があり、 他のオブジェクト・オリエントなプログラミング言語にみられるような似通った文法となっています。

ES5のコンストラクタ関数を使った”疑似クラス”を定義する方法と、ES6のクラスを使った定義の方法を紹介します。

ES5’s constructor functions, the “new” keyword

JavaScript 5 (あるいはそれ以前のバージョン)では、コンストラクタ関数と呼ばれている疑似クラスのテンプレートを定義できます。 次のことを除いて、関数を作るのと同じ文法を使います。

  • 1. 規則で、名前の最初の文字を大文字にします。通常の関数ではなく、コンストラクタ関数であることがわかります。作ろうとするオブジェクトクラスの名前は名詞とします。例: Person, Vehicle, Enemy, Product, Circle, Ball, Player, Hero, etc.
  • 2. 新しいオブジェクトを作るために new キーワードを使います: 例 (Car, Hero, Ball, Product はコンストラクタ関数の名前): var car = new Car('Ferrari', 'red'); var luke = new Hero('Luke Skywalker', 'rebels"); var ball1 = new Ball(10, 10, 20, 'blue'); // x=10, y=10, radius = 20, color = 'blue' var p1 = new Product('Epson printer P1232', '183', 'Mr Buffa'); // ref, price, customer etc.
  • 3.関数のパラメータは、コンストラクタ・パラメータで: 作ろうとしている新しいオブジェクトはこのパラメータを初期プロパティ値として使います。 ヒーローを生成するときには、名前や、どちら側、などを与えなくてはなりません。
  • 4. プロパティ名とメソッド名は this キーワードをを使い定義します。 覚えておく: 文法は、シングルトン/単純なオブジェクトの定義に使ったものとは異なります。 ":" も無く、プロパティ間の "," もありません。 通常の関数と同じように"=" と ";" を使います。 例: function Hero(name, side) { this.name = name; this.side = side; this.speak = function() { console.log("My name is " + this.name + " and I'm with the " + this.side); } } "Hero"という名のコンストラクタ関数では、このように宣言されたプロパティがあります: this.name this.side; そして このように宣言されtqメソッド: this.speak = function() {...}
  • 5. プロパティがコンストラクタ関数パラメータを使って初期化されることはよくあることです。 つまり新しく生成されたオブジェクトはプロパティの初期値を取得します。 この場合、this キーワードを使い、コンストラクタ関数パラメータとプロパティを区別します: 例: function Hero(name) { this.name = name; ... }

Full interactive example that uses a constructor function

Look at the JS code. This time we created multiple objects using a "constructor function.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 function Hero(name, side) {
  this.name = name;
  this.side = side;

  this.speak = function() {
    return "<p>My name is " + this.name +
      ", I'm with the " + this.side + ".</p>";
  }
}

var darkVador = new Hero("Dark Vador", "empire");
var luke = new Hero("Luke Skywalker", "rebels");
var ianSolo = new Hero("Ian Solo", "rebels");

function makeHeroesSpeak() {
  document.body.innerHTML += darkVador.speak();
   document.body.innerHTML += luke.speak();
   document.body.innerHTML += ianSolo.speak();
}

Creating objects using the new ES6 classes

ES5のコンストラクタ関数の文法は読みにくかったりします。 規則を無視してコンストラクタ関数の最初の文字を大文字にしない人がいたら、コードは動きますが、 通常の関数なのかどうか分かりずらいものになります。

ES6 は class キーワード と constructor キーワードを作りました。

Main changes:

  • 1. クラスは、クラス名の前に class キーワードを使い分かりやすく定義されます。
  • 2. たった一つのコンストラクタは、パラメータを持つ constructor キーワードを使い定義される。
    • コンストラクタはキーワード new をオブジェクトが生成されるときに実行される。
      例: let h1 = new Hero('Ian Solo', 'rebels');

      これは下記の例で constructor(name, side) を呼び出します。
  • 3. メソッドは単にパラメータを持った名前で定義されます。function キーワードは使いません。
    例: speak() {...} in the source code below.

Here is the new version of the Hero “template”, this time with the ES6 class syntax:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Hero {
    constructor(name, side) {
        this.name = name; // property
        this.side = side; // property
    }

    speak() { // method, no more "function"
        return "<p>My name is " + this.name +
               ", I'm with the " + this.side + ".</p>";
    }
}

var darkVador = new Hero("Dark Vador", "empire");

Interactive example that uses an ES6 class to create Star Wars heroes

Look at the JS code. This time we created multiple objects using an ES6 class named Hero.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Hero {
  constructor(name, side) {
    this.name = name;
    this.side = side;   
  }

  speak() {
    return "<p>My name is " + this.name +
      ", I'm with the " + this.side + ".</p>";
  }
}

var darkVador = new Hero("Dark Vador", "empire");
var luke = new Hero("Luke Skywalker", "rebels");
var ianSolo = new Hero("Ian Solo", "rebels");

function makeHeroesSpeak() {
  document.body.innerHTML += darkVador.speak();
   document.body.innerHTML += luke.speak();
   document.body.innerHTML += ianSolo.speak();
}

You must declare a class before using it!

関数とは違い、クラスは使う前に宣言されていなくてはならない。

関数の宣言とクラスの宣言の重要な違いは、関数の宣言は、下から引き上げられるが、クラスの宣言はできない。 つまり関数は、ソースコードの中で宣言する前でも呼び出すことができるということです。 ES6のクラスの場合には当てはまりません。

You first need to declare your class and then access it, otherwise code like the following will throw a ReferenceError:

Incorrect version => you try to create an instance of a class before it has been declared:

1
2
3
var p = new Rectangle(); // ReferenceError

class Rectangle {...}

Correct version =>

1
2
3
class Rectangle {...}

var p = new Rectangle(); // WORKS !

Creating objects with functions (factories)

We have already seen three different ways to create objects (literals, constructor functions and ES6 classes)

Objects can be created as “literals” :

1
var darkVador = { firstName:Dark, lastName:Vador};

Objects can be created with the keyword new and a constructor function or an ES6 class:

1
var darkVador = new Hero(Dark Vador, empire);

Here is a new one: オブジェクトはオブジェクト(ファクトリ)を返す関数によっても生成できます

1
2
3
4
5
6
7
8
9
10
function getMousePos(event, canvas) {
    var rect = canvas.getBoundingClientRect();
    var mxx = event.clientX - rect.left;
    var my = event.clientY - rect.top;

    return { // the getMousePos function returns an object. It’s a factory
        x: mx,
        y: my
    }
}

And here is how you can use this:

1
2
3
var mousePos = getMousePos(evt, canvas);

console.log("Mouse position x = " + mousePos.x + " y = " + mousePos.y);

The call to getMousePos returns an object that has an x and a y property.

Static properties and methods

Class properties and methods vs. instances’ properties and methods

時々、クラスのインスタンスではなく、クラスにくっついているメソッドがあります。

例えば、 Heroクラスを思い浮かべてください、そして何人のヒーローを作成したか知りたいとします。 もし一人も生成されていないなら、Dark Vador のような、クラスのインスタンスに属するプロパティを使えないのは明らかです: darkVador.getNbHeroes();

しかしながら、オブジェクト・オリエントなプログラミング言語には、今まで見てきた”インスタンス・プロパティ”と”インスタンス・メソッド”と全く同じ、”クラス・プロパティ”と”クラス・メソッド”の概念があります。Hero.getNbHeroes() は、 “ねえ, クラス Hero、君のクラスを使って、何人のヒーローを作ったか教えて”ということです。クラスメソッドは”class behavior”を定義し,インスタンスメソッドはインスタンスの動作を定義します。darVador.speak(); は、”ねえ, Dark Vador, 何か話して”という意味です。 私は Dark Vador に語り掛け、彼から出てくる何かを期待しています “I’m your father, Luke!”のようなことを。

プロパティということでは同じで。もしクラス Hero に、nbHerosCreated という名のプロパティがあるとします、それは、インスタンスのではなく、クラスのDNAを表してします。 Heroクラスは、生成されたヒーローの数を持っているといえます。 Dark Vadorは、名前と、帝国側であることを持っていますが、Dark Vadorは、彼が作ったヒーローの数は持っていません。クラス・プロパティとインスタンス・プロパティがあるのです。

The static keyword is used for defining class methods

Class methods

どの様に区別するのでしょうか。 static キーワードを使います。 static キーワードが前についたメソッドを見たなら、それはクラスプロパティかクラスメソッドだということです。

static キーワードはクラスの static メソッドを定義します

Static メソッドは、クラスをインスタンス化しなくても呼び出せます

そして、クラスのインスタンスからは呼び出せません

結果として: 本体内でインスタンス・プロパティを使うな

Static メソッドはしばしばアプリケーションのためのユーティリティ関数を作るために使われます。

Class properties

クラス・プロパティは、そのクラスの宣言の後に定義されなくてはならず、クラス名の後に . 演算子をつけ、その後にプロパティ名をつけて定義されなくてはならない。例: Point.nbPointsCreated

クラス・プロパティを定義するもう一つの方法があります(static getters と setters を使います)

Example of creation and use of class methods and properties using an ES6 class

Source code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Point {
   constructor(x, y) {
      this.x = x;
      this.y = y;
      // static property
      Point.nbPointsCreated++;
   }

   // static method
   static distance(a, b) {
      const dx = a.x - b.x;
      const dy = a.y - b.y;

      return Math.sqrt(dx*dx + dy*dy);
   }
}
// static property definition is necessarily outside of the class with ES6
Point.nbPointsCreated=0;

// We create 3 points
const p1 = new Point(5, 5);
const p2 = new Point(10, 10);
const p3 = new Point(12, 27);

document.body.innerHTML += "<p>Distance between points (5, 5) and (10, 10) is " +
                           Point.distance(p1, p2) + "</p>";
document.body.innerHTML += "Number of Points created is " + Point.nbPointsCreated;

[Advanced] ES6 getters and setters

Definition

ゲッタとセッタと呼ばれる特別なメソッドを使って、クラス・プロパティを定義できます。 これらのメソッドは、プロパティに値を設定仕様としたときや、プロパティにアクセスして何かをやらせたいときに 何らかのチェックをさせることを可能にします(値が小文字だとして、それを大文字にして表示するとか)。

この特別な関数はゲッタとセッタと呼ばれ、キーワードの get と set に続けてプロパティの名前を書いて宣言されます。

Typical use (lines 7 and 11):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Person {
    constructor(givenName, familyName) {
        this.givenName = givenName; // "normal name"
        this._familyName = familyName; // starts with "_"
    }

    get familyName() {
        return this._familyName.toUpperCase();
    }

    set familyName(newName) {
        // validation could be checked here such as
        // only allowing non numerical values
        this._familyName = newName;
    }

    walk() {
        return (this.givenName + ' ' + this._familyName + ' is walking.');
    }
}

let p1 = new Person('Michel', 'Buffa');
console.log(p1.familyName); // will display BUFFA in the devtool console
                            // this will call implicitly get familyName();
p1.familyName = 'Smith';    // this will call implicitly set familyName('Smith');

get familyName() {…}を宣言するとします、これは、名前が”familyName”のプロパティを明示的に定義をします、 そして、オブジェクトがそのクラスのインスタンスであるなら、object.familyNameを使いプロパティにアクセスできます。 22-25行目。p1.familyNameの値を表示しているところは、明らかに get familyName()を呼び出します. 一方、p1.familyName = ‘Smith’; は set familyName(‘Smith’);を呼び出します。

get familyName() は、familyNameという名のプロパティを明示的に定義していますが、規約では、this._familyNameを使い値を保存します(アンダースコアがプロパティ名の先頭についています)。