あらゆるプログラミング言語は、それぞれ異なったデータ構造を持っています。この記事では、JavaScript で使用可能な組み込みデータ構造の一覧と、他のデータ構造の構築にも使えるように、それらがどのような性質を持ち合わせているかについて述べることにします。また可能である場合は、他のプログラミング言語におけるデータ構造との対比も行います。
動的型付け
JavaScript は弱い型付けあるいは動的型付けの言語です。これは、あらかじめ変数の型を宣言する必要がないということです。型はプログラムを処理している間に、自動的に決められます。また、異なる型で同じ変数を持つことができるということでもあります:
var foo = 42; // この foo は Number var foo = "bar"; // この foo は String var foo = true; // この foo は Boolean
データ型
最新の ECMAScript 標準仕様では 7 つのデータ型が定義されています。
プリミティブ値
object 型をのぞく全ての型は不変 (immutable) な値 (変更することができない値) として定義されています。特筆すべき点としては、 string 型が(Cなどとは違って)不変な値である点です。これらの型の値を、プリミティブ値 (primitive value) と呼びます。詳細は Strings の節で触れることとします。
Boolean 型
Boolean は論理的な実体であり、true
、false
の 2 つの値があります。
Null 型
Null 型は値が null
の 1 種類しかありません。詳しくは null
および Null をご覧ください。
Undefined 型
値を代入していない変数の値は、undefined
になります。詳しくは undefined
および Undefined をご覧ください。
Number 型
ECMAScript 標準仕様によると、この型は「"倍精度 64 ビット形式による IEEE 754 値"」 (-(253 -1) から 253 -1 の間の数値) となります。特筆すべきは整数であるとは定義されていないという点です。さらに言うならば、この型は浮動小数点数値にもなりますし、+Infinity
、-Infinity
、NaN
(not-a-number) といった記号的な値も持ち合わせています。
+/-Infinity
より大きいまたは小さい値を調べるために、定数 Number.MAX_VALUE
および Number.MIN_VALUE
を使用できます。また ECMAScript 6 より、Number.isSafeInteger()
、Number.MAX_SAFE_INTEGER
、Number.MIN_SAFE_INTEGER
を使用して数値が倍精度浮動小数点数値の範囲内にあるかを調べることもできます。この範囲を超えた数値は、JavaScript では信頼できません。
Number 型には、2 種類の表現を持つ数値がひとつだけあります。それは 0 であり、-0 および +0 で表します ("0" は +0 の別名です)。実用上、影響はほとんどありません。例えば、+0 === -0
は true
です。ただし、ゼロ除算を行った場合は違いに気づくでしょう:
> 42 / +0 Infinity > 42 / -0 -Infinity
数値はたいてい数値としてのみ表されるものの、JavaScript はいくつかのバイナリ演算を提供しています。そのため、ビットマスクを用いて、一つの数値で複数の真偽値を表現することも可能です。しかしながら、JavaScript が (真偽値の配列や真偽値のプロパティを複数持つオブジェクトのような) 真偽値の集合を表現する手段を提供していることや、ビットマスクはコードの可読性、わかりやすさ、保守性を大きく損なってしまうことから、この機能はたいてい、バッドプラクティスとして考えられています。ローカルストレージの容量的制約への対処や、ビット単位での転送量を考える必要のある極限状態など、非常に特殊なケースにおいてはこうしたテクニックが必要となるでしょう。このテクニックは、あくまでも最適化が必要な場合の最終手段としてのみ考慮すべきです。
String 型
JavaScript の String
型は、テキストデータを表すために使用します。これは、16 ビット符号なし整数値の "要素" の集合体です。文字列内の各要素は、文字列内の位置を占めます。最初の要素はインデックス 0、次の要素がインデックス 1 となっていきます。また文字列の長さは、内部にある要素の数です。
C のような言語とは異なり、 JavaScript の文字列は 不変 (immutable) です。これは一度生成した文字列は変更が不可能であるということを意味しています。しかしながら、オリジナルの文字列を操作することで別の文字列を生成することは可能となっています。例えば:
- 個別に文字を抜き出す、または
String.substr()
を用いて、オリジナルの部分文字列を切り出す - 連結演算子 (
+
) またはString.concat()
を用いて、2 つの文字列を連結する
「文字列より」のコードに注意!
文字列を用いて複雑なデータ構造を表現したいと思うこともあるでしょう。文字列は以下のような使い勝手の良さを備えています。
- 文字列連結によって複雑な文字列の構築が簡単である
- 文字列はデバッグが簡単である(出力される情報は常に文字列の一種である)
- 文字列は(インプットフィールド、 ローカルストレージの値、
XMLHttpRequest
のresponseText
など)数多くの API において基準として扱われており、全てを文字列で処理したいと思わせる
理屈の上では、文字列を用いることで、あらゆるデータ型の表現が可能ではあります。ですが、あまり良い考えとはいえません。例として、セパレータ文字を使用することでリストの模倣が可能です(役割としては JavaScript の配列の方がよっぽど適していますが)が、残念なことにセパレータ文字が「リスト」の 要素となってしまった場合、そのリストは破綻します。エスケープした文字を使用することでこの問題に対処することは可能ですが、そのルールをすべてに用意する必要がある上、用途に応じて適切な道具を用いなかったことによるメンテナンスコストの増大を招きます。
文字列型の使用はテキストデータや記号データを表すのには向いていますが、別のデータ構造を表す為に文字列のパースや正しい抽象化を用いる必要がある場合には向いていないと言えるでしょう。
Symbol 型
Symbols は、ECMAScript Edition 6 における JavaScript の新機能です。Symbol はユニークかつ不変なプリミティブ値で、オブジェクトのプロパティ識別子として使用します (後述)。Symbol のことを atom と呼ぶプログラミング言語があります。Symbol は、C の名前付き列挙型 (enum) と対比することもできます。詳しくは Symbol および JavaScript における Symbol
オブジェクトラッパーをご覧ください。
Object
コンピュータ科学において、オブジェクトは識別子によって参照可能なメモリ内の値とみなせます。
プロパティ
JavaScript において、オブジェクトはプロパティのバッグとみなせます。オブジェクトリテラル構文では、限定されたプロパティセットを初期化します。プロパティは追加および削除が可能です。プロパティの値は、他のオブジェクトを含むあらゆる型の値が設定可能であり、これにより複雑なデータ構造の構築が可能となっています。プロパティは、キー値を使用して識別します。キー値は String 値または Symbol 値です。
オブジェクトには、特定の属性を持つ 2 種類のプロパティがあります。それは、データプロパティとアクセサプロパティです。
データプロパティ
キーと値を関連づけており、以下の属性を持ちます:
属性 | 型 | 説明 | 既定値 |
---|---|---|---|
[[Value]] | JavaScript の任意の型 | プロパティにアクセスすると取り出される値です。 | undefined |
[[Writable]] | Boolean | false であれば、プロパティの [[Value]] は変更できません。 |
false |
[[Enumerable]] | Boolean | true であれば、プロパティは for...in ループで列挙されます。Enumerability and ownership of properties もご覧ください。 |
false |
[[Configurable]] | Boolean | false であれば、プロパティは削除できません。また、[[Value]] および [[Writable]] 以外の属性を変更できません。 |
false |
属性 | 型 | 説明 |
---|---|---|
Read-only | Boolean | ES5 の [[Writable]] 属性の状態を反転したもの。 |
DontEnum | Boolean | ES5 の [[Enumerable]] 属性の状態を反転したもの。 |
DontDelete | Boolean | ES5 の [[Configurable]] 属性の状態を反転したもの。 |
アクセサプロパティ
値を取り出しまたは保存するための 1 つまたは 2 つのアクセサ関数 (get および set) とキーを関連づけており、以下の属性を持ちます:
属性 | 型 | 説明 | 既定値 |
---|---|---|---|
[[Get]] | Function オブジェクトまたは undefined | 値に対して get アクセスが実行されると、関数が引数なしで呼び出されてプロパティの値を取り出します。get もご覧ください。 |
undefined |
[[Set]] | Function オブジェクトまたは undefined | 指定したプロパティを変更しようとしたときに、代入する値を引数に含めて関数が呼び出されます。set もご覧ください。 |
undefined |
[[Enumerable]] | Boolean | true であれば、プロパティは for...in ループで列挙されます。 |
false |
[[Configurable]] | Boolean | false であれば、プロパティの削除やデータプロパティの変更はできません。 |
false |
注記: 属性は通常 JavaScript エンジンが使用しますので、それらに直接アクセスすることはできません (詳しくは Object.defineProperty() をご覧ください。)。これが、属性に角括弧が 2 つついている理由です。
「通常の」オブジェクトおよび関数
JavaScript オブジェクトはキーと値を所持しています。キーは文字列 (または Symbol
) ですが、値はなんでもかまいません。これにより、オブジェクトはハッシュマップの様相を呈します。
関数は呼び出し可能という付加機能を持つ、通常のオブジェクトです。
Date
日時についての表現を考慮するとき、組み込みの Date
オブジェクトのユーティリティメンバを用いるのが最も妥当でしょう。
インデックス付きコレクション: Array (配列) および Typed Array (型付き配列)
Array は整数値をキーにするプロパティと length プロパティの間に特殊な関係の存在する、標準オブジェクトです。加えて述べるなら、配列は、配列を操作するのに便利ないくつかのメソッドを提供する Array.prototype
を継承しています。例えば indexOf
(配列中の値の探索) や push
(配列への要素の追加) などです。これにより配列はリストまたはセットとして表現するために必要な機能を備えることとなります。
Typed Arrays は ECMAScript Edition 6 における JavaScript の新機能であり、バイナリデータバッファについて配列状のビューを提供します。以下の表で、同等の C データ型を見つけるのに役立つでしょう:
TypedArray オブジェクト
型 | サイズ (バイト数) | 説明 | Web IDL 型 | 同等の C 型 |
Int8Array |
1 | 8 ビット長、2 の補数方式の符号付き整数値 | byte |
int8_t |
Uint8Array |
1 | 8 ビット長、符号なし整数値 | octet |
uint8_t |
Uint8ClampedArray |
1 | 8 ビット長、符号なし整数値 (切り詰め) | octet |
uint8_t |
Int16Array |
2 | 16 ビット長、2 の補数方式の符号付き整数値 | short |
int16_t |
Uint16Array |
2 | 16 ビット長、符号なし整数値 | unsigned short |
uint16_t |
Int32Array |
4 | 32 ビット長、2 の補数方式の符号付き整数値 | long |
int32_t |
Uint32Array |
4 | 32 ビット長、符号なし整数値 | unsigned long |
uint32_t |
Float32Array |
4 | 32 ビット長、IEEE 方式 浮動小数点数 | unrestricted float |
float |
Float64Array |
8 | 64 ビット長、IEEE 方式 浮動小数点数 | unrestricted double |
double |
キー付きコレクション: Map, Set, WeakMap, WeakSet
これらのデータ構造は、キーとしてオブジェクトへの参照を持ち、ECMAScript Edition 6 で導入されました。WeakMap
および Map
がオブジェクトと値を関連づける一方で、Set
および WeakSet
はオブジェクトのセットを表します。Map と WeakMap の相違点としては、前者がキーであるオブジェクトの列挙が可能であるのに対し、後者がガベージコレクションに最適化されている点です。
Map と Set は純粋に ECMAScript 5 での実装も可能ではありますが、("完全" な意味での) オブジェクトの比較は不可能であり、キーとしたオブジェクトの線形探索が必要なことによるパフォーマンス上の問題があります。(WeakMap を含む) Map と Set のネイティブな実装では対数を用いることにより概して定数時間でのキーおよび関連する値の探索が可能となっています。
従来ならば、DOM ノードにデータを結び付ける場合は、オブジェクトに直接プロパティを設定するか、data-*
属性を用いるのが普通でした。これらの手法は同じコンテクストで実行されるあらゆるスクリプトからデータの利用が可能であるため、不都合な面を持ち合わせていました。Map および WeakMap を使用することにより、オブジェクトへのプライベートなデータの結びつけを簡単に行うことができます。
構造化データ: JSON
JSON (JavaScript Object Notation) は軽量なデータ交換形式であり、JavaScript 由来ですが多くのプログラミング言語で使用されています。JSON は万能なデータ構造を構築します。詳しくは JSON および JSON
をご覧ください。
標準ライブラリに含まれる他のオブジェクト
JavaScript には、組み込みオブジェクトの標準ライブラリがあります。それらのオブジェクトについては、リファレンスをご覧ください。
typeof
演算子を使用して型を検出する
typeof
演算子は、変数の型を知るのに役立ちます。詳細および使用例については、リファレンスページをご覧ください。