
前回の続き。
今回はクラスの基礎について。
目次
クラス(Class)
前回話した通り、クラスとは「設計図」を意味している。
そして、オブジェクト指向(の言語)は、このクラスを用いて書くことが殆ど。
そもそも「オブジェクト指向」とは
エンジニアの方に言わせれば、このテーマ1つで1冊書けるとのこと。おそロシア。
個人的に、一番しっくりきた説明を備忘録として載せておく。
オブジェクト指向とはクラスを用いて関連するデータとメソッドを組み合わせ、設計をしっかりして構成しましょうという考え方です。 オブジェクト指向の概念を理解するのは非常に難しいと思いますが、簡単に言うと構造化して使いまわしやすくしましょうということです。
https://www.willstyle.co.jp/blog/2435/
つまり、いくつかのクラスを用意し、組み合わせていくことで、設計・構築していく考え方のこと、といったニュアンスだろう(多分)。
JavaScriptのクラスとは
javaScriptにはもともとクラスの概念がなかったが、ES6からクラス構文が搭載された。
定義方法としては二通りある。
// パターン1:クラス宣言をして書く
class Box {
constructor(width, height) {
this.width = width;
this.height = height;
}
}
// パターン2:クラス式で書く
//// 名前なし
let Box = class {
constructor(width, height) {
this.width = width;
this.height = height;
}
};
console.log(Box.name); // "Box"
/// 名前あり
let Box = class Boxdetail {
constructor(width, height) {
this.width = width;
this.height = height;
}
};
console.log(Box.name); // "Boxdetail"
うん、、「constructorってなんだよ」って話だけど、一旦おく。
まず、「パターン1・2」の違いについて。
パターン1のやり方は「クラス宣言」と呼ばれる
// パターン1:クラス宣言
class クラス名 {
constructor(x, y) {
// コンストラクタ定義
// メソッド定義
}
}
そして、パターン2は「クラス式」と呼ばれる。
// パターン2:クラス式
let 変数 = class クラス名 {
//コンストラクタの定義
//メソッドの定義
};
クラス式で宣言する場合、名前なし・ありのどちらでもで可。
また、名前ありの場合、クラスの名前はクラス内のローカルとして扱われ、クラスのnameプロパティで取得できる。
【JavaScript】class構文によるクラス表現【自己学習】
https://qiita.com/OXIIVIA/items/e2b14cab1bd1309d2171
クラス構文におけるコンストラクタ
さっきから気になってたconstructor。
なんか色々調べたけど、以前書いたコンストラクタと意味合い一緒っぽい?
constructor (引数) {}
このコンストラクタは特別なメソッドで、 クラスから生成されるインスタンスのプロパティを設定する。
constructorは1つのクラスに1つだけ。 なんか2つ置くとSyntaxErrorで怒られる。
クラス構文におけるメソッド
メソッドとは…オブジェクトのプロパティである関数。
定義の仕方は下記。
// 定義の仕方は下記
// メソッド名 {}
class Box {
constructor(width, height) {
this.width = width;
this.height = height;
}
descWidth() {
console.log('箱の横幅は' + this.width + 'です'); // インスタンスのプロパティの値を利用し、メソッドを定義する
}
descBox() {
this.descWidth(); // 同じクラスの他のメソッドを利用
console.log('箱の高さは' + this.height + 'です');
}
}
let box1 = new Box('100', '200'); // インスタンスを生成
box1.descWidth();
box1.descBox();
Hoisiting(ホイスティング、巻き上げ)
JavaScrptのクラス構文の話をする上では、Hoisting(ホイスティング、巻き上げ)を理解せなあかん。
Hoisting とは
超ざっくりに言えば、「定義・宣言をする前に使うことができる」ということ。
「Hoisting」が起きる例として、関数宣言が挙げられる。
なんぞ?って話だが、簡単にいうと関数は、コード上で定義する前に使用することができる。
function Lupin(name) {
console.log('あばよ~!' + name + '!');
}
Lupin('とっつぁ~ん'); // あばよ~!とっつぁ~ん!
でも動くし、
Lupin('とっつぁ~ん'); // あばよ~!とっつぁ~ん!
function Lupin(name) {
console.log('あばよ~!' + name + '!');
}
でも動く。
また、変数も同様にHoistingは動作する。
で、これでハマることが多いから注意が必要。
知らないと怖い「変数の巻き上げ」とは?
https://analogic.jp/hoisting/
ここの記事の内容を超要約すると、「変数宣言の際、varを利用するとHoistngが起きる可能性が生じる。」ということ。
もうちょい詳しく言うと、「Hoistingでは定義だけ巻き上げられ、初期化に関してはスルーされてしまう。」ということ。
さっきの例を出す。
var name = 'とっつぁ~ん';
function Lupin() {
console.log('あばよ~!' + name + '!'); // あばよ~!undefined!
var name = 'じげ~ん';
console.log('あばよ~!' + name + '!'); // あばよ~!じげ~ん!
}
Lupin(); // あばよ~!じげ~ん!
は?ってなるよね。大丈夫、俺もなった。
とっつぁんどこ行った?
先述の通り、Hoisitingされるのは宣言部分だけ。
なので、実際に起きていることは下記と同義になってしまうのだ。
var name = 'とっつぁ~ん';
function Lupin() {
var name; // 変数の宣言だけ行われ、初期化部分に関してはスルーされる。
console.log('あばよ~!' + name + '!'); // あばよ~!undefined!
var name = 'じげ~ん';
console.log('あばよ~!' + name + '!'); // あばよ~!じげ~ん!
}
Lupin(); // あばよ~!じげ~ん!
だから、1回目は「とっつぁ~ん」でなく、undefined
になってしまう。
対策としては、const
やlet
を使うのが手っ取り早い(てか推奨されてる)。
ただその前に「そもそも関数の先頭で宣言する~」とか、保守ルール意識しましょうって話にも繋がるので、それは意識したい。
クラス構文におけるHoisting
話が逸れてしまったけど、クラス構文の話に戻る。
結論から言うと、『クラス宣言 / クラス式』にHoistingは起きない。
class Lupin {
constructor(name) {
this.name = name;
}
message() {
console.log('あばよ~!' + this.name + '!');
}
}
let lupin1 = new Lupin('とっつぁ~ん');
lupin1.message(); // あばよ~!とっつぁ~ん!
なら動く。
しかし、
let lupin1 = new Lupin('とっつぁ~ん');
lupin1.message(); // 「ねーよ馬鹿!」って怒られてしまう(RerenceError)
class Lupin {
constructor(name) {
this.name = name;
}
message() {
console.log('あばよ~!' + this.name + '!');
}
}
だから、JavaScriptでクラス構文を使う際には、「クラスはこれです!」って先に書かなきゃダメ。
クラスの継承
クラス構文もprototypeと同様に、継承を利用できる。
class Goku {
constructor(name) {
this.name = name;
}
Greeting() {
console.log(`オッス!オラ${this.name}!`);
}
}
class Goku2 extends Goku {
constructor(name) {
super(name);
}
greeting() {
super.Greeting();
console.log(`いっちょやってみっか~!`);
}
}
let goku2 = new Goku2('悟空');
goku2.greeting(); // オッス!オラ悟空!いっちょやってみっか~!
ちょいちょい新キャラが登場しているので振り返る。
まず、クラスの継承はextends
を使う。
class クラス名 extends 継承元クラス {
/* ここに記述を書く */
}
superによる上書き
そして、super
。
class Goku2 extends Goku {
constructor(name) {
super(name);
}
greeting() {
super.Greeting();
console.log(`いっちょやってみっか~!`);
}
}
色々調べたけどこれは「お約束」ぽい。はい、ほんとはよくないね。スンマセン。
super
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/super
できる限り詳しく説明すると、「コンストラクタを上書きするとき」「メソッドを上書きするとき」に使われる。
ただ、「上書き」といっても、実際のところ完全に置き換える訳ではなく(それだと継承の意味なくなるし)、その上に組み立てる・微調整する上で、用いられることがほとんど。
class Saiyajin {
constructor(message) { }
get enemy() {
return 'フリーザーーーッッ!!'
}
get friend() {
return 'クリリン'
}
angry() {
console.log('ゴゴゴゴゴゴゴ…')
}
}
class Goku1 extends Saiyajin {
constructor(message) {
super(); // 親のコンストラクタを呼び出す
this.words = message + super.enemy; // 親のコンストラクタを上書き
}
angry() {
super.angry(); // 親のメソッドを呼び出す
console.log(`${this.words}`);
}
}
goku1 = new Goku1('俺は怒ったぞーーー!');
goku1.angry(); // ゴゴゴゴゴゴゴ… 俺は怒ったぞーーー!フリーザーーーッッ!!
class Goku2 extends Saiyajin {
constructor(message) {
super();
this.words = super.friend + message;
}
angry() {
super.angry();
console.log(`${this.words}`);
}
}
goku2 = new Goku2('のことかーーー!');
goku2.angry(); // ゴゴゴゴゴゴゴ… クリリンのことかーーー!
気を付けたいこと何点かも併せて書いてく。
簡単な覚え方としては、super()
で呼び出す際は先に書かないと怒られる。
- 親の
constructor
を参照したいとき - 親のメソッドを参照したいとき
- 子クラスの
constructor()
内でthis
を使いたいとき
class Goku1 extends Saiyajin {
constructor(message) {
// this.words = message + super.enemy;
// super(); // 先にかかなきゃダメ
super();
this.words = message + super.enemy;
}
angry() {
console.log(`${this.words}`);
}
}
class Goku {}
class Gohan extends Goku {
constructor(kamehamaha) {
// this.kamehamaha = kamehamaha; // これダメ
// super();
super();
this.kamehamaha = kamehamaha; // これでおk
}
}
以上、クラスについての「超」基礎的な話。
クラスについてはまだまだ勉強しなきゃいけないことがあるので、随時更新していきまっす。