2021年6月5日

プリミティブのメソッド

JavaScript はプリミティブ(文字列、数値など)をオブジェクトのように扱うことができます。また、それらはメソッドも提供しています。この後すぐに学んでいきますが、最初に、どのように動作するのか確認しましょう。なぜなら、プリミティブはオブジェクトではないからです(このチャプターでそれをさらに明確にしていきます)。

プリミティブとオブジェクトの主な違いを見ていきましょう。

プリミティブ

  • プリミティブ型の値です。
  • プリミティブ型は 7 つあります: string, number, bigint, boolean, symbol, null, undefined

オブジェクト

  • プロパティとして複数の値を保持することができます。
  • {} で作ることができます。例えば: {name: "John", age: 30}。JavaScriptでは他の種類のオブジェクトもあります。関数もオブジェクトです。

オブジェクトの最も良いところの1つは、そのプロパティの1つとして関数を保持することができることです:

let john = {
  name: "John",
  sayHi: function() {
    alert("Hi buddy!");
  }
};

john.sayHi(); // Hi buddy!

上の例では、sayHi メソッドをもつ john オブジェクトを作りました。

日付、エラー、HTML要素などで動作するような、多くの組み込みのオブジェクトが既に存在し、それらは異なるプロパティとメソッドを持っています。

しかし、これらの機能にはコストがかかります!

オブジェクトはプリミティブよりも “重い” です。内部の仕組みをサポートするために追加のリソースを必要とします。しかし、プロパティやメソッドはプログラミングをする上で非常に有用であり、JavaScriptエンジンはそれらの負担を減らすために最適化を試みます。

オブジェクトとしてのプリミティブ

JavaScriptの作成者が直面するパラドックスは次の通りです。:

  • 文字列や数字のようなプリミティブに対してやりたいことがたくさんあります。 それらをメソッドとして実現することは素晴らしいことでしょう。
  • プリミティブはできるだけ高速、かつ軽量でなければなりません。

解決策は少々野暮ですが次の通りです:

  1. プリミティブは依然としてプリミティブです。要望どおり、単一の値です。
  2. 言語は、文字列、数値、真偽値そしてシンボルのメソッドやプロパティにアクセスすることができます。
  3. 必要に応じて、追加の機能を提供する特別な “オブジェクトラッパー” が作られ、その後、破棄されます。

“オブジェクトラッパー” はプリミティブ型毎に異なり、String, Number, Boolean, Symbol と呼ばれます。従って、それらは異なるメソッドのセットを提供します。

例えば、大文字化された文字列を返す str.toUpperCase() というメソッドがあります。

次のように動作します:

let str = "Hello";

alert( str.toUpperCase() ); // HELLO

シンプルですよね? str.toUpperCase() で実際に何が起こっているのでしょう:

  1. 文字列 str はプリミティブです。なので、プロパティへアクセスした瞬間に、文字列の値を知る特別なオブジェクトが作られ、それは toUpperCase() のような便利なメソッドを持っています。
  2. メソッドは実行され、新たな文字列を返します(alert で表示されたものです)
  3. 特別なオブジェクトは破棄され、プリミティブの str だけが残ります。

従って、プリミティブは依然として軽量なままですが、メソッドを提供できます。

JavaScriptエンジンはこの処理を高度に最適化しています。余分なオブジェクトの作成を完全にスキップするかもしれません。しかし、それは依然として仕様に準拠し、あたかもオブジェクトを作成したかのように動作しなければなりません。

数値は自身のメソッドを持っています。例えば、指定された精度に数値を丸める toFixed(n) です:

let n = 1.23456;

alert( n.toFixed(2) ); // 1.23

具体的なメソッドはチャプター 数値文字列 で見ましょう。

コンストラクタ String/Number/Boolean は内部でのみ利用します

Javaなどの言語は new Number(1) または new Boolean(false) のような構文を使うことで明示的にプリミティブのための “ラッパーオブジェクト” を作ることが出来ます。

JavaScriptにおいても、歴史的な理由から可能ですが、強く 推奨しません。いくつかの場所で物事がおかしなことになっていくでしょう。

例:

alert( typeof 1 ); // "number"

alert( typeof new Number(1) ); // "object"!

オブジェクトは if では常に true なので、アラートが表示されます。

let zero = new Number(0);

if (zero) { // zero は true, オブジェクトだからです
  alert( "zero is truthy?!?" );
}

一方、new をつけずに同じ関数 String/Number/Boolean を使うことは、普通であり役に立ちます。それらは値を対応する型へ変換します: 文字列、数値、もしくは真偽値(プリミティブ)

例えば、これはまったく問題ありません:

let num = Number("123"); // string から number へ変換
null/undefined はメソッドを持ちません

特別なプリミティブ nullundefined は例外です。それらは対応する “ラッパーオブジェクト” を持たずメソッドを提供しません。ある意味では “最もプリミティブ” です。

このようなアクセスはエラーになります:

alert(null.test); // error

サマリ

  • プリミティブは、nullundefined の例外を除いて、多くの役立つメソッドを提供します。次のチャプターで詳細を学んでいきます。
  • 形式的には、それらのメソッドは一時的なオブジェクトを通して行われます。しかしJavaScriptエンジンは内部的に最適化するようチューニングされているため、呼び出しのコストはかかりません。

タスク

重要性: 5

次のコードを考えてください:

let str = "Hello";

str.test = 5;

alert(str.test);

あなたはどう思いますか?それはうまくいくでしょうか? 何が表示されますか?

動かしてみましょう:

let str = "Hello";

str.test = 5; // (*)

alert(str.test);

結果は2種類あります::

  1. undefined
  2. エラー

なぜでしょう? (*) で何が起きているのかもう一度見てみましょう:

  1. str のプロパティにアクセスされたとき、“ラッパーオブジェクト” が作られます。
  2. プロパティの操作はそこで行われます。なので、オブエジェクトは test プロパティを取得します。
  3. 操作が終了し、“ラッパーオブジェクト” は消えます。

なので、最後の行では str はそのプロパティへのトレースを持っていません。

しかし、ブラウザによっては、プログラマをさらに制限し、プロパティをプリミティブに割り当てることを禁止することもあります。そのため実際には (*) でエラーになることがあります。しかし、それは仕様からは少し離れています。

この例は、プリミティブがオブジェクトではないことを明確に示しています。

それらは単にデータを格納することができません。

すべてのプロパティ/メソッド操作は一時オブジェクトのヘルプによって実行されています。

チュートリアルマップ