JavaScriptのインタフェースについて

2021/03/24に公開

インタフェースの概念

例えば、ポルシェは乗用車であり、乗用車は四輪車であるという階層構造がこの世界にはあります。集合の記号で表現すると「四輪車 ⊇ 乗用車 ∋ ポルシェ」といったようになっています[1]。プリウスも乗用車ですので、「四輪車 ⊇ 乗用車 ∋ プリウス」です。四輪車にも、乗用車の他、大型バスや建設車両、荷車などの種類があります。

ここで、「ポルシェ」というのは実際に運転できる車(モノ)を指していますが、「乗用車」という名前の車はありません。「乗用車」とは、ハンドル・アクセル・ブレーキなど、車に共通した部品とその機能を集めた “概念” です。「哺乳類 ⊇ 人間 ∋ ナカモトサトシ」でも「食器 ⊇ カトラリー ∋ スプーン」でもほとんど何でもそうですが、人間が認識する世界の階層構造の末端には実体(モノ)があり、それより上層にあるものはすべて概念です。

そして、ポルシェだけが持っている部品や機能もありますが、多くの部品と機能は「乗用車」という枠組みの中で共通しており、その共通部分の操作方法は大体同じです。だから運転免許を持っている人は、ポルシェでもプリウスでも「乗用車」であれば運転することができます。

このように、概念的に共通した部品と機能の集まり(もしくは操作の集まり)のことを インタフェース/interface といいます[2]。私たちが、乗用車やガスコンロや ATM などを(機種が違っても)迷いなく使えるのは、それらが共通したインタフェースを備えているからです。

DOM とインタフェース

この「インタフェース」という概念はプログラミングの世界でも重要です。ここからクラスや継承といった “オブジェクト指向” の話が始まるのが定石ですが、本記事は JavaScript が対象なので DOM を題材に説明したいと思います。

HTML ドキュメントの構造を表現する DOM も、車と同じように階層構造になっています。

ノード(Node)
  └ 要素(Element)
    └ HTML要素(HTMLElement)
      └ p要素(HTMLParagraphElement)

JavaScript のコード document.querySelector('p') で取得される通称「p エレメント」は、上図からわかるように、HTMLParagraphElement オブジェクトです[3]。そして、 その HTMLParagraphElement オブジェクトは、一つ上位層の HTMLElement インタフェースに従って実装されたオブジェクトです。ややこしくなってきましたね。

ここで、さきほどの車の例を思い出してみましょう。ポルシェにもプリウスにも、ハンドル・アクセル・ブレーキがあります。これらの部品と機能がないと「乗用車」とはいえないので、これはポルシェやプリウスに備わったインタフェースというよりも、その上位概念である「乗用車」に備わったインタフェースであるといえます。つまり、ハンドル・アクセル・ブレーキを要することやその操作方法については「乗用車」のインタフェースに定義されており、ポルシェやプリウスはその仕様に従って製造されます。

これと同じように、p 要素にも li 要素にも「コンテントを持っている(textContent プロパティ)」や「子要素を追加できる(appendChild メソッド)」といった共通する部品と機能があります。したがって、これらのプロパティやメソッドは、p エレメントや li エレメントの上位概念である HTMLElement のインタフェースとして定義しておいたほうがよいですよね。
ということで、p エレメント(HTMLParagraphElement)は HTMLElement インタフェースを実装したオブジェクト…ということになります。

ここで、「HTMLElement オブジェクト」ではなく「HTMLElementインタフェース」と呼ぶのは、HTMLElement が「乗用車」と同じく、“概念” であって “実体” ではないからです。JavaScript の「オブジェクト」はコードの中で直接扱える “実体” です(変数に代入したりできますよね)。したがって、MDN Web Docs では「HTMLElement オブジェクト」とはいわず、「HTMLElement インタフェース」と表現されています[4]

さて、車の例に戻ると、「乗用車」の上に「四輪車」という概念がありました。例えば、「4 輪のタイヤを持つ」という条件は「四輪車」が持つべきインタフェースでしょう。乗用車だけでなく、荷車のようにハンドルもアクセルもないモノでも 4 輪タイヤなら四輪車ですから。このように、四輪車 ⊇ 乗用車 ∋ ポルシェ という階層の中で、共通のインタフェースはできるだけ上階層で定義されていきます。

DOM も同様に、Node インタフェース ⊇ Element インタフェース ⊇ HTMLElement インタフェース ∋ HTMLParagraphElement オブジェクトというように段階的にインタフェースが定義されています。例えば、textContent プロパティや appendChild メソッドが定義されているのは最上層の Node インタフェースです。querySelector メソッドは Element インタフェースで定義されています。ちなみに、HTMLParagraphElement オブジェクトだけが持っているプロパティやメソッドはなく、ひとつ上の HTMLElement インタフェースと同じものです。

オブジェクトとインタフェース

ここまでの説明の中で HTMLParagraphElement は実体を伴う「オブジェクト」と記述してきました。もし途中で HTMLParagraphElement について MDN Web Docs を調べた人がいたら、オブジェクトではなくインタフェースと書かれているではないか…と思ったかもしれません。

ここで改めて考えると、「ポルシェ」という言葉も概念を指す場合のあることに気づきます。車体番号の付いた ◯◯ さんのポルシェはモノですが、「ポルシェが欲しい」といったときには「ポルシェ」という概念を指します。その場合、「ポルシェ」のインタフェース(仕様)に従って、実際に人が乗ることのできるポルシェ個体が製造されます。

同様に、HTMLParagraphElement もオブジェクトを指すときもあれば、インタフェースを指すときもあります。以下のように、querySelector メソッドで取得された定数 p1 に入っている HTMLParagraphElement はオブジェクトです。ポルシェと同じく実体はいくつでも作り出すことができます。

const p1 = document.querySelector('p[id="p1"]');
const p2 = document.querySelector('p[id="p2"]');

Java や C# などクラスベースのオブジェクト指向言語には、インタフェースを定義するための構文が用意されています。一方、JavaScript にはインタフェース用の構文がないので、JavaScript におけるインタフェースというのは単なる言葉上の決まり事です。実際には JavaScript では各インタフェースも結局オブジェクトとして実装されており、プロトタイプチェーンという仕組みを使ってプロパティやメソッドを下層のオブジェクトに引き継いでいます(たぶん)。

このように、JavaScript では抽象的な概念であるインタフェースもオブジェクトとして実現されており、実装上はインタフェースもオブジェクトです。『文系大学生のための JavaScript 入門』の中で「インタフェース」という言葉を使わず、すべて「オブジェクト」で説明しているのはそのためです。

『文系大学生のための JavaScript 入門』の読者へ

『文系大学生のための JavaScript 入門』の中では次のように記述しています。

これまで getElementById メソッドなどで JavaScript 側に呼び出していた「エレメント」というのは、この DOM ツリーでいう 要素ノード のことです。
(中略)
DOM ツリーの要素ノード(≒Element オブジェクト)を getElementById メソッドで取得したり、createElement メソッドで生成したり、appendChild メソッドで DOM ツリーにつなげたりすることで HTML ドキュメントを変化させています。

正確には、getElementById メソッドなどで取得できる「エレメント」とは、要素ノードの下層にある HTMLParagraphElement などです。これを Element であるとしたのは、入門者にとってそのくらいの抽象度で理解しておいたほうが混乱しないと考えたからです(HTMLElement は読みにくいですし)。ポルシェを乗用車と呼んでも差し支えないように、p エレメントを Element オブジェクトと呼んでも構わないはずですから(たぶん)。

また、「Element インタフェース」ではなく「Element オブジェクト」としたのは、プログラングで実際に使用するエレメント(getElementById メソッド等の戻り値)はオブジェクトであり、JavaScript では概念的にしか存在しない「インタフェース」を登場させると入門者は混乱すると考えたからです。正確には、「要素ノード」は概念であり、JavaScript でいうところの Element インタフェースに当たります。

脚注
  1. ここでは「ポルシェ」という言葉を(車名ではなく)モデル名を表すものとして使用しています。「718 ケイマン」などと書くと冗長だからです。気になる人は好きなポルシェのモデルに読み替えてください。 ↩︎

  2. “インタフェース” とは「接点」を表す言葉であり、情報技術の文脈では「ものごとの境界となる部分と、その境界でのプロトコルを指す(Wikipedia)」ものですが、ここではそのプロトコル(約束事)がある程度共通化されている状態を「インタフェース」と呼んでいます。 ↩︎

  3. 『文系大学生のための JavaScript 入門』の中では、「エレメントは Element オブジェクト」だと書いています。それについては最期に書いていますので読み進めてください。 ↩︎

  4. MDN Web Docs で「オブジェクト」ではなく「インタフェース」と表現されているのは、単に DOM の仕様の表現を使っているだけかもしれませんが。 ↩︎

Discussion