HTML・CSS

【コピペしながら理解する】JavaScriptのclass構文

2022/03/02

以前「【コピペしながら理解する】自作JavaScriptスライダー」という記事を書かせていただき、ありがたいことに当サイトで1、2位を争うほど読まれる記事となりました。

こちらの記事は、ライブラリなどを使用しない「Pure JavaScript」でsliderを制作するという内容です。

私がJavaScriptを学習し始めたころに書いたコードであり、とにかくコードが動くことだけを考えて書いたものを紹介しているのが特徴です。

そのため、実務で使える内容というよりは部分的な知識として利用していただく内容です。

ただ、私が体験してきた学習過程において「初学者のコードや説明が、ベテランとは違う視点で学習初期の理解を助けてくれる」という傾向があったため、このような情報を発信しています。

そこで今回もJavaScript初学者に向けて、初学者目線での情報を発信していきます。

今回の内容はモダンJavaScriptの鬼門である「class構文」についてです。

前回同様sliderを題材として書いていきますので、前回の記事や、その他ネットにあるsliderのコードと比べてみると理解しやすいかもしれません。

また、あくまで理解を助ける情報であるため、言い回しの平易化や抽象化によって情報の不正確さが発生します。

十分加味した上でご利用いただき、学習初期の手助けとなれましたら幸いです。

class構文とは

JavaScriptのclass構文という概念について、軽く触れておきます。

最初に注意しておくべきことは、HTMLやCSSで使う「class」の概念とは全くの別物ということです。

JavaScriptのclass構文は、新しい概念として理解する必要があります。

class構文を何かに喩えてみると、料理のレシピ、服の型紙などに近いかもしれません。

料理のレシピだとすると、一つ決まったテンプレートを用意することでアレンジがしやすくなるということがあります。

カレーだとするならば、元のレシピに対して水を牛乳に、肉を魚に替えることで簡単にミルクシーフードカレーにアレンジが可能です。

これは基本のテンプレートとなるレシピがあるからアレンジを効かせやすいという構造になっています。

服の型紙の場合も同様に、ひとつ決まったコートの型紙があるとするなら、同様のサイズのウールのコート、ポリエステルのコートなどに展開して作成することが可能です。

JavaScriptのコードも、「class構文という形式に則ってコードを書くことで、汎用性の高い便利なテンプレートを作ることができる」というイメージです。

class構文をマスターすれば、一度作ったテンプレートをアレンジする場合の作業効率が上がったり、一部を書き換えるだけでメンテナンスできるコードの書くことが可能です。

また、class構文の概念は他の言語でも多く使われている概念なので、別言語に挑戦するときにも理解を助けてくれます。

完成形の確認

実際にclass構文で制作したsliderを見てみます。

完成形は以下を目指します。

See the Pen Untitled by shinobi (@shinobi-hattori) on CodePen.

 

JSのタブをクリックし、コードの一番下を見ていただくと、

const slider = new Slider(“horizontal”, ‘1’, 3000);

という部分があります。

このhorizontalの部分をverticalに変えてみます。

const slider = new Slider(“vertical”, ‘1’, 3000);

すると、スライドが縦に動くようになります。

See the Pen Untitled by shinobi (@shinobi-hattori) on CodePen.

 

1単語の記述を変えるだけで、大きく機能を変えることができました。

このように、class構文の特性を活かした「使い回しやすく、メンテナンスしやすい」sliderを作っていきます。

構造を把握する

大まかなイメージを掴んだところで、今度は具体的にコードの全体構造を把握していきます。

class構文の基本的な書き方のひとつが、以下のような書き方です。

classを宣言 {
   コンストラクタと書く{

最初に読み込んで欲しい内容を書く

   }

メソッド(関数)を書く

}
classを実行するために呼び出し文を書く

 

最初はわかりやすいように機能を最低限に絞ったsliderで構成してみます。

下の状態だと、単純に自動でスライドする機能だけ実装されています。

See the Pen Untitled by shinobi (@shinobi-hattori) on CodePen.

class宣言について

では、上から順番に記述方法を説明していきます。

まずは、「classの宣言」というものをします。

JavaScriptコード一番上の

class Slider {

という部分です。

単純に「class」と書いたあと、中身の機能を表している名前をつけるだけです。

ただ、classを宣言する際の命名は慣習的に1文字目は大文字にすることが多いようなので、大文字スタートの名前で命名します。

また、class構文であることを宣言する書き方はいくつかあり、以下のように変数に代入する「class式」という書き方もあります。

const Slider = class {

初めの理解としては、基本的にはどのような宣言方法でも問題ないと思っていただいて大丈夫です。

中身は同じように動いてくれます。

コンストラクタについて

classを宣言したあとは、「コンストラクタ」を書いていきます。

書き方はclassの宣言に続いて、

constructor() {
  //中身
}

と書いていくだけです。

コンストラクタは「オブジェクトを生成し、初期化時に実行される特殊なメソッド」のように説明される場合がありますが、ここが理解しにくい部分かと思います。

ざっくり理解する場合であれば、「ページを読み込むときに最初に実行してくれるもの」であり、「これから記述する関数に使うものをあらかじめ定義しておくところ」ぐらいの言葉に言い換えられるかもしれません。

デモのコンストラクタの中身には、

・querySelectorなどでHTML要素を取得し、操作できるようにしておく
・index番号の加減で動く仕組みのslider用に、indexを宣言しておく
・HTMLの仕様が変更されたとして、必ずsliderに必要になるstyleを書いておく

ということを書いています。

他にも具体的な例をいくつか見ることで、どのようなことを記述する場所なのかが把握できてくると思います。

書き方のルールとしては、定義していくものの頭に全て「this」とつけるということです。

this.slider = document.querySelector(‘#slider’);
this.frame = document.querySelector(‘#slider-frame’);
this.items = document.querySelectorAll(‘.slider-item’);
this.index = 0;

getについて

次に、「get」という記述について説明していきます。

コンストラクタの記述後すぐに書かれている関数です。

get head() {
  return this.index == 0;
}

get tail() {
  return this.index == this.items.length – 1;
}

getと同時に使われることが多いsetというものもあります。

これらgetとsetは、class構文の記述において必ず必要になるものではありません。

それぞれコンストラクタで定義したthis.〇〇と紐づけて様々な値を取得したりする書き方なのですが、これとは別に関数を作ってしまえば必要な機能は実装できてしまいます。

しかし、getとsetをわざわざ利用すると、class構文の中で主軸となる値を取得していることがコードを見ている人に伝わりやすくなることがあります

今回の場合だと、sliderの最初のを表す「head」という部分と、最後のコンテンツを表す「tail」という部分を取得していることが明確にわかります。

そのため、class構文にある程度慣れてきた後に、メンテナンス性の向上などを考えて利用してみるといいかもしれません。

必要な関数を用意する

続いて、sliderが動くように関数を書いていきます。

用意した関数は以下の通りです。

//頭に戻る関数
rewind() {
  this.moveTo(0);
}

//次に進む関数
next() {
   if (!this.tail) {
     this.moveTo(this.index + 1);
   }
}

//前に戻る関数
prev() {
  if (!this.head) {
    this.moveTo(this.index – 1);
   }
}

//index番号によって、動きを司る関数
moveTo(index) {
   this.index = index;
   const offset = (100 / 1) * this.index;
   this.frame.style.transform = `translateX(-${offset}%)`;
}

//自動で動かす関数
start() {
   setInterval(() => {
    if (this.tail) {
       this.rewind();
    } else {
       this.next();
     }
   }, 3000);
}

変数や関数を呼び出し式を書いていく際にも、「this.〇〇」という表記が使われます。

このような書き方をすることで、class内で定義してきた変数や関数と紐付けすることができます。

get headと書いた部分も、getを取り除いて「this.head」という記述をすることで値を取得することができます。

呼び出し

最後に、今まで書いてきたclassを専用関数で呼び出します。

この呼び出しがないとclass構文の中身は動きません。

呼び出し方は

const slider = new Slider();
slider.start();

のように変数で名前をつけてあげて、代入先に「new class名();」です。

今回は自動スライド関数も同時にスタートさせるため、「slider.start();」でstart関数も実行しています。

以上が基本的な全体構造です。

引数で汎用性を高める

ここから、最初に紹介した完成形のような汎用性のある機能を付け足していきます。

今回sliderに実装する機能は、

・縦横スライドの切り替え
・表示されるアイテム数(カラム数)の切り替え
・自動スライドのスライド間隔の切り替え

としました。

このようなものを自由に切り替えられるsliderにすることによって、わざわざclass構文で書く恩恵が受けられます。

機能を追加していく際には、「コンストラクタ、呼び出し関数の引数に入れる値から考える」という方法がおすすめです。

どういうことかというと、切り替え可能になった機能は呼び出し関数の引数で内容を切り替えています。

完成形のデモの例でいうと、以下のような紐付けをしています。

//コンストラクタ
constructor(direction, preview, interval){
//内容省略
}

//呼び出し
const slider = new Slider(“horizontal”, ‘1’, 3000);

コンストラクタの引数がそれぞれ

・direction:縦横スライドの切り替え
・preview:表示されるアイテム数(カラム数)
・interval:自動スライドのスライド間隔

に連動するようになっています。

上の例では、呼び出しの際に「horizontal」が「direction」に代入されてスライド方向は横に、「1」が「preview」に代入されてアイテム数は1に、「3000」が「interval」に代入されて自動スライド時間は3000ミリ秒という指示を出しています。

このように、最終的にはコンストラクタの引数と、class呼び出し関数の引数が連動するように中身も追記していきます

順番にコンストラクタから見ていきます。

constructor(direction, preview, interval){
//内容省略
}

まずは機能を想定する引数をセットしました。

direction, preview, intervalの3つです。

次にコンストラクタの中身を追記します。

constructor(direction, preview, interval) {
  this.slider = document.querySelector(‘#slider’);
  this.frame = document.querySelector(‘#slider-frame’);
  this.items = document.querySelectorAll(‘.slider-item’);
  this.direction = direction;
  this.preview = preview;
  this.interval = interval;
  this.index = 0;
  this.slider.style.height = ‘100%’;
  this.slider.style.overflow = ‘hidden’;
  this.frame.style.height = ‘100%’;
  this.frame.style.display = ‘flex’;
  this.frame.style.transition = ‘0.3s’;

  switch (this.direction) {
    case ‘horizontal’:
  this.frame.style.flexFlow = ‘row’; break;
    case ‘vertical’:
  this.frame.style.flexFlow = ‘column’; break;
  }

  this.items.forEach(elem => {
    switch (this.direction) {
      case ‘horizontal’:
  elem.style.minWidth = `calc(100%/${this.preview})`; break;
      case ‘vertical’:
  elem.style.minHeight = `calc(100%/${this.preview})`; break;
     }
   });
}

引数は単純に「this.direction = direction」のような形で代入します。

また、縦横のスライドの向きに合わせてCSSを切り替える記述をしました。

次はメソッドの書き替えです。

moveTo(index) {
 this.index = index;
 const offset = (100 / this.preview) * this.index;

 switch (this.direction) {
    case ‘horizontal’:
 this.frame.style.transform = `translateX(-${offset}%)`; break;
    case ‘vertical’:
 this.frame.style.transform = `translateY(-${offset}%)`; break;
  }
}

start() {
  setInterval(() => {
    if (this.tail) {
       this.rewind();
  } else {
      this.next();
     }
   }, this.interval);
}

メソッドの追記には、moveTo関数のCSSをswitch文で切り替えるようにしています。

これにより、引数horizontalで呼び出した場合はtranslateX方向に移動、引数verticalで呼び出した場合はtranslateY方向に移動するようになりました。

また、start関数に記述されていたミリ秒の部分をthis.intervalに置き換えることによって、呼び出し関数の引数に好きな数字を入れれば簡単に変更できるようになりました。

ここまでの結果が以下のようになります。

スライド方向は縦である「vertical」、表示アイテム数は「2」、自動スライド時間は「3000」で呼び出しています。

See the Pen Untitled by shinobi (@shinobi-hattori) on CodePen.

まとめ

以上のように、最初は最低限の機能でclass構文を記述し、そこから汎用性、メンテナンス性を加味しつつ、拡張機能を足していくのがおすすめです。

慣れていけば、最初から機能拡張の引数を加味したコードを書いていくことが可能です。

また、class構文で記述するjsファイルは1つのファイルとして作成し、利用する際にメインファイルに読み込ませるようにする場合が多いです。

そうすることで、sliderではない別のパーツに取り替えたりすることも容易になり、メンテナンス性が向上します。

このようなWebサイトやアプリに使う部品を分けて作成していく考え方は「コンポーネント設計」と呼ばれ、モダンな設計として主流の考え方になってきています。

最後に、もう一度ボタン機能なども追加した完成形を見てみます。

少し全体の構造が見えてきているのではないでしょうか。

See the Pen Untitled by shinobi (@shinobi-hattori) on CodePen.

 

今回の例で言えば、テキストの色、スライダーの背景色やデザインなど、コンストラクタの引数に入れてさらに拡張できる余地はあります。

しかし、あまりに高機能にすると重くなり、メンテナンス性も落ちてきます。

バランスを見て設計をして、使いやすいclass構文を目指すのがよいとされています。

長くなりましたが、本記事が少しでもJavaScript理解の助けになれば幸いです。

おすすめ記事