JavaScriptにおけるオブジェクトの扱い

混乱を避けるために先に説明しますが、JavaScriptにクラス機能が追加されたのはES2015以降です。それ以前は「クラス」という概念は存在しませんでした。JavaやC#などのオブジェクト指向言語において、標準ライブラリやビルトインクラスと呼ばれているものは、JavaScriptではビルトイン"オブジェクト"と呼ばれています。

型の種類

ES2020では次の9つの型が定義されています。

  • プリミティブ(基本型)
    • undefined
    • Boolean
    • Number
    • String
    • BigInt
    • Symbol
    • null
  • オブジェクト
    • Object
    • Function

少し意外ですが、String型はプリミティブ(基本型)です。そして、後述するStringオブジェクト、Numberオブジェクト、Booleanオブジェクトなどは上記のObjectオブジェクトの一部に含まれています。

型を調べる typeof演算子

typeof演算子を使うと型を調べることができます。次の例ではプリミティブ(基本型)のnumber, string, booleanを判定しています。

typeof 1; //"number"
typeof "あ"; //"string"
typeof true; //"boolean"

いわゆるラッパーオブジェクトにあたるプリミティブのオブジェクト版も存在します。

//!! 以下は非推奨の書き方です !!
typeof new Number(1); //"object"
typeof new String(1); //"object"
typeof new Boolean(1); //"object"

今回は例示のためにnew演算子を使用しています。通常、Number、String、Booleanオブジェクトをnew演算子で宣言すると、オブジェクトになってしまい、===で比較するのが難しくなりますので、このような宣言方法を使うことはしません。

以下のようにオブジェクトをメソッドのように呼び出すと、プリミティブを生成することもできます。

typeof Number(1); //"number"
typeof String(1); //"string"
typeof Boolean(1); //"boolean"

こちらのほうが使い勝手は良いですが、やっているのは面倒なことをしてプリミティブを生成しているだけなので、普通にプリミティブを宣言したほうが楽です。
しかも、次に説明するプリミティブの奇妙な振る舞いの仕様があるので、ラッパーオブジェクト版は使わずにプリミティブとして宣言するだけで事足りてしまいます。

プリミティブの奇妙な振る舞い

プリミティブ(基本型)は奇妙な振る舞いをします。次の例を見てください。

"12345".charAt(2); //出力:3

プリミティブとして宣言しているはずなのに、なぜかStringオブジェクトが持つcharAtメソッドを呼び出すことができています。
JavaやC#などのプログラミング言語では、プリミティブはメソッドを持つことはできなかったはずです。これはどういうメカニズムなのでしょうか?

...結論を申し上げると、プリミティブをオブジェクトのように扱うと一時的にオブジェクトに変換することができます。つまり、メソッドを呼び出そうとするとプリミティブのStringはオブジェクトのStringへと変換されるわけです。そして、終わるとまたプリミティブへと自動的に変換されます。

なんだか奇妙でややこしいですが、このような仕様からプリミティブとオブジェクトの区別がすごく付きにくいのがJavaScriptの特徴です。

参考
Ecma International
String - JavaScript | MDN