【STEP.06】
JavaScriptのデータ型をマスターしよう!

データ型とは

データ型とは、データの種類のことです。

JavaScriptでは、「xyz」のような文字列、1,2,3のような数値、true(真)/false(偽)のような論理値など、実にさまざまなデータをスクリプトの中で扱えます。

プログラミング言語には、このデータ型を強く意識するものと、逆にほとんど意識する必要がないものがあります。

たとえば、JavaやC#のような言語は前者に該当するので、数値を格納するために用意された変数に文字列を格納することは許されません。

これらの言語で、変数とデータ型は常にワンセットなのです。

一方、JavaScriptは後者に属する言語で、データ型について寛容です。

最初は文字列を格納しておいた変数に数値を格納しても構いませんし、その逆も可能です。

変数(入れ物)のほうが、代入される値に応じて形や大きさを変えてくれるわけです。

したがって、JavaScriptでは以下のコードも正しいものとして扱うことができます。

let x = 'Hello、JavaScript!';
x = 100;

したがって、開発者がデータ型を意識しなければならない局面はそれほど多くありませんが、まったく意識しなくてよいというわけではありません。

厳密な演算や比較を行う局面では、それなりにデータ型を念頭に置く必要があります。

JavaScriptで扱うことができる主なデータ型

JavaScriptで扱うことができる主なデータ型は次の通りです。

【JavaScriptで扱うことができる主なデータ型】
分類データ型概要
基本型数値型(number)±4.94065645841246544×10-324
~±1.79769313486231570×10-308
文字列型(string)クォートで囲まれた0個以上の文字
真偽型(boolean)true(真)/false(偽)
シンボル型(symbol)
ES2015
シンボルを表す
特殊型(null/undefined)値が定義されていない、存在しないことを表す
参照型配列(array)データの集合(各要素はインデックス番号で取得)
オブジェクト(object)データの集合(各要素は名前で取得)
関数(function)連なった処理のかたまり

JavaScriptのデータ型は、大きく基本型参照型の2つに分類できます。

両者の違いは「値を変数に格納する方法」です。

まず、基本型の変数には、値そのものが直接格納されます。

それに対して、参照型の変数は、その参照値(値を実際に格納しているメモリ上のアドレス)を格納します。

基本型と参照型の違い

数値型(number)

数値リテラルとは

JavaScriptで扱うことができる数値のことを、数値リテラルといいます。

数値リテラルは、大きく整数リテラルと浮動小数点リテラルに分類できます。

数値リテラルとは

まずは、整数リテラルから見ていきましょう。

整数リテラルとは

整数で日常的に使用するのは10進数リテラルです。

2進数/8進数/16進数で表現する場合は、リテラルの頭にそれぞれ「0b」(ゼロとビー)、「0o」(ゼロとオー)、「0x」(ゼロとエックス)を付ける必要があります。

2進数では、0~1の値を、8進数では0~7の値を、16進数では0~9の数値を加えて、A(a)~F(f)までの英字を使用できます。

それ以外の値を指定した場合には、「Invalid or unexpected token」(不正な文字です。)のようなエラーとなるので、注意が必要です。

10進数、2進数、8進数、16進数

具体的な例を見てみましょう。

// 10進数リテラル
let num1 = -10;
console.log(num1);
// 結果:-10
 
// 2進数リテラル
let num4 = 0b111;
console.log(num4);
// 結果:7
 
// 8進数リテラル
let num3 = 0o500;
console.log(num3);
// 結果:320
 
// 16進数リテラル
let num2 = 0xFF;
console.log(num2);
// 結果:255

ES2015以前のJavaScriptでも「0666」(頭にゼロ)のようにすることで、8進数リテラルを表現できました。

しかし、これは標準の機能ではなく、実装によって対応が分かれることから、利用すべきではありません。

8進数リテラルを表現するには、ES2015の「0o~」を利用してください。

また、Strictモードでも旧式の8進数表記はエラーとして扱われます。

なお、リテラル接頭辞の「0b」「0o」「0x」は大文字/小文字を区別しないので、それぞれ「0B」「0O」「0X」でも間違いではありません。

ただし、大文字Oは数字の0と区別がつきにくいため、一般的には「0o」のように小文字での表記がおすすめです。

次に、浮動小数点リテラルを見てみます。

浮動小数点リテラルとは

浮動小数点リテラルでは、一般的な小数点数だけでなく、指数表現をとることもできます。

// 一般的な小数点数
let num1 = 1.2345;
console.log(num1);
// 結果:1.2345

// 指数表現
let num2 = 0.12345e-10;
console.log(num2);
// 結果:1.2345e-11

指数表現は「<仮数部>e<符号><指数部>」形式で表し、

「<仮数部>×10の<符号><指数部>乗」

によって本来の値に変換できます。

一般的には、非常に大きな(小さな)値を表すために利用します
(12000000000よりは1.2e10のほうが明快です)。

指数を表す「e」は大文字/小文字を区別しないので、0.12345e-10は0.12345E-10としても同じ意味です。

MEMO
指数表現の正規化
指数表現では、同じ12345を
  • 1234.5e1
  • 12.345e3
  • 1.2345e4
のように複数のパターンで表記できます。 しかし、可読性の観点から、これはあまり望ましい状態ではないので、一般的には仮数部を「0.」+「0以外の数値」で始まるように表すことで、表記を統一します。 この例であれば、0.12345e5です。 先頭のゼロは省略できるので、「.12345e5」としても同じ意味です。

まとめ

以上のように、数値リテラルにはさまざまな表現方法がありますが、本質的にはこれらの違いは見かけ上のものにすぎません。

JavaScriptにとっては、「0b10010」(2進数) 、「0o22」(8進数)、 「0x12」(16進数) 、 「.18e2」(指数) は、いずれも同じく10進数の18なのです。

どの表記を選ぶかは、その時々の読みやすさに応じて決めるべきです。

文字列型(string)

文字列リテラルとは

JavaScriptで扱うことができる文字列のことを、文字列リテラルといいます。

文字列リテラル(string型の値)は、シングルクォート(')、または、ダブルクォート(")でくくる必要があります。

たとえば、以下はいずれも正しい文字列リテラルです。

'Hello、JavaScript!'
"Hello、JavaScript!"

文字列の中にシングルクォート/ダブルクォートがある場合には、それぞれ文字列に含まれないほうのクォートでくくるようにします。

×  'He's Hero' 文字列の中にシングルクォートが含まれているので不可
○  "He's Hero" 文字列の中にシングルクォートが含まれているが、ダブルクォートで囲っているので可

シングルクォートとダブルクォートいずれを使うかは、前後の対応関係が取れている限り、特に意識しなくて問題ありません。

ただし、JavaScriptでは文字列にタグ(の属性)を含むケースがあります。

そして、属性はダブルクォート(")でくくることが多いことを考えると、文字列はシングルクォート(')でくくるのが望ましいと考えられます。

let tag = '<img src="hoge.png" />';

もしもダブルクォート(")でくくっていた場合、文字列の終端を正しく認識できないので、エラーとなります。

\"」のようにエスケープすることもできますが、数が増えた場合には文字列そのものが読みづらくなることから、望ましくありません。

✕:let tag = "<img src="hoge.png" />";

△:let tag = "<img src=\"hoge.png\" />";

文字列を出力する方法

文字列を出力するためのサンプルコードを見てみましょう。

<div id="result"></div>
let str = 'こんにちは 文字列!';

// ダイアログを表示
window.alert(str);

// コンソール(開発者ツール)に表示
console.log(str);

// ページ上(id="result")である要素に表示
document.getElementById('result').textContent = str;

// ページ上に表示
document.write(str);
まず、window.alertメソッドは、ダイアログボックスに文字列をポップアップします。
ダイアログボックス

アプリの中で利用する場合には、jQuery UI(https://jqueryui.com/)などでよりリッチなダイアログを実装するため、主にデバッグ用途で、コードの途中経過を確認するのによく利用します。

ただし、複数の文字列を確認する場合には、いちいちダイアログを閉じなければならないのが面倒です。

そのような場合は、console.logメソッドを利用します。

console.logメソッドはブラウザー付属の開発者ツールに文字列を出力します。
コンソール画面

アプリの中で文字列をページに反映するならば、textContentプロパティが便利です。

同じくページに文字列を表示するdocument.writeメソッドもあります。

こちらは表示場所を選択できない(=その場に表示する)ことから使い勝手も悪く、現在、使われることはほとんどありません。

真偽型(boolean)

JavaScriptでは、真偽型(boolean型)が用意されており、truefalseというリテラルで表現できます。

真偽値は、Booleanコンストラクターで生成することもできますが、こちらは冗長であるだけでなく、挙動に誤解を招きやすいことから利用すべきではありません。

// let flag = false;(本来あるべき記法)

let flag = new Boolean(false);
if (flag) {
  console.log('表示されない...はず');
}
// 結果:表示されない...はず

false値であるにも関わらず、Booleanコンストラクターで生成されたオブジェクトはtrueと見なされているのです。

これは、JavaScriptではnull以外のオブジェクトはtrueと見なされるという性質があるからです。

当然、これは直感的な挙動ではなく、ゆえにコンストラクター構文を利用すべきではありません。

次のように「let flag = false;」と記述した場合には、こうした問題は起こりません。

let flag = false;
if (flag) {
  console.log('表示されない...はず');
}
// 結果:(なにも表示されない)
MEMO
ラッパーオブジェクトのコンストラクターは利用しない。
JavaScriptが標準で提供する組み込みオブジェクトの中でも、特に文字列(String)、数値(Number)、論理値(Boolean)を扱うためのオブジェクトのことを、ラッパーオブジェクトといいます。

ラッパーオブジェクトとは、単なる値に過ぎない基本型のデータを包んで(=ラップして)、オブジェクトとしての振る舞いを提供するためのオブジェクトです。

ただし、JavaScriptでは基本型⇔ラッパーオブジェクト間の変換を自動的に行うため、開発者がこれを意識することはありません。

あえてコンストラクターを利用することは冗長ですし、上記のような問題を引き起こすこともあるため、避けてください。

シンボル型(symbol)

ES2015では、これまでのStringNumberBooleanなどの型に加えて、新たにSymbolという型が追加されました。

Symbolとは、名前の通り、シンボル(モノの名前)を作成するための型です。

一見すると、文字列にも似ていますが、文字列ではありません。

シンボルの基本

まずは、シンボルを実際に作成し、生成されたシンボルの内容を確認してみましょう。

let sym1 = Symbol('sym');
let sym2 = Symbol('sym');

console.log(typeof sym1);
// 結果:symbol
console.log(sym1.toString());
// 結果:Symbol(sym)
console.log(sym1 === sym2);
// 結果:false

シンボルを生成するのは、Symbol命令の役割です(let sym1 = Symbol('sym');let sym2 = Symbol('sym');)。

インスタンスを生成するという意味で、コンストラクターにも似ていますが、new演算子で「new Symbol('sym')」のように表すことはできません(TypeErrorとなります)。

Symbol命令

Symbol([desc])
desc    シンボルの説明

引数descはシンボルの説明(名前)です。

引数descが同じシンボルでも、別々に作成されたシンボルは別物と見なされる点に注意してください。

上の例であれば、sym1sym2は、いずれも引数descsymですが、===演算子での比較では異なるものと見なされます(console.log(sym1 === sym2); //結果:false)。

また、シンボルでは文字列/数値への暗黙的な型変換はできません。

よって、以下はいずれもエラーとなります。

console.log(sym1 + '');
// 結果:エラー(Cannot convert a Symbol value to a string)

console.log(sym1 - 0);
// 結果:エラー(Cannot convert a Symbol value to a number)

ただし、boolean型への変換は可能です。

console.log(typeof !!sym1);
// 結果:boolean

シンボルの具体例

シンボルは、たとえば列挙定数を表す状況で利用できます。

たとえば、SPRINGSUMMERAUTUMNWINTERのように、名前による識別だけに関心があるような状況です。

const SPRING = Symbol();
const SUMMER = Symbol();
const AUTUMN = Symbol();
const WINTER = Symbol();

シンボルを利用することで、0、1…のような便宜的な値を割り当てる必要がなくなりますし、SPRINGSPRINGとしてしか表現できなくなるので、コードにあいまいさがなくなります。

シンボルを使わなかった場合の例と比較してみましょう。

const SPRING = 0;
const SUMMER = 1;
const AUTUMN = 2;
const WINTER = 3;

このような定数を比較するには、定数名/数値リテラルいずれを利用してもかまいません。

let season = SPRING;
if(season === SPRING){...} 本来あるべきコード
if(season === 0){...} でも、リテラルとも比較できてしまう

コードの可読性を鑑みればリテラルと比較できる状態は好ましくありません。

なにより「const MONDAY = 0;」のような(値は等しいが)別の意味を持った定数が現れた場合には、以下のようなコードも許容してしまう可能性があります。

if(season === MONDAY){...} 季節は月曜日???

このような状況はシンボル定数によって解決します。

SPRINGSPRINGとしか等しくないので、条件式がtrueとなるのは「if(season === SPRING){...}」だけです。

特殊型(undefined/null)

JavaScriptには、値がないことを表す値としてundefined(未定義値)とnull(ヌル)とがあります。

時として、区別があいまいになることがあるので、ここで用途の違いをまとめておきます。

undefined(未定義値)とは

undefinedは、ある変数の値がまだ定義されていないことを表す値で、主に、以下のような状況で返されます。

  1. ある変数が宣言されているが、初期化されていない
  2. 未定義のプロパティを参照した
  3. 関数で戻り値がなかった

具体的な例は、以下の通りです。

let data;
console.log(data);
// 結果:undefined(初期化されていない変数)

let obj = {hoge: 123, piyo: 456};
console.log(obj.data);
// 結果:undefined(存在しないプロパティ)

let fn = function () {};
console.log(fn());
// 結果:undefined(戻り値のない関数)

nullとは

nullは、該当する値が存在しない(=空である)ことを表す値です。

undefined(未定義)と区別しにくいですが、以下のような使い分けをします。

まず、文字列を表示するoutputのような関数があった場合、関数はあくまで出力を目的としたもので、結果を期待しているわけではないので、戻り値はundefined(未定義)です。

一方、テキストからURL文字列を抽出するgetUrlのような関数があったとします。

その際、URLが見つからなかった場合、undefined(未定義)を返すのは不自然です。

値がない(見つからなかった)という意図を明確にするならば、null(空)を返すべきでしょう。

また、変数に格納したオブジェクトを明示的に破棄する場合にも、nullを利用します。

変数の中身を空にするわけです。

let obg = new Hoge();
obj = null;
// オブジェクトを破棄

以上がundefinednullとの大まかな違いですが、実際のアプリでは区別があいまいな状況もあります。

まずは、意図した空値はnullで、値そのものを期待していない場合はundefinedで、という基本的な区別をします。

配列(array)

数値型/文字列型/真偽型が、単一の値を格納するための型であったのに対して、配列とは複数の値をまとめて管理するための型です。

複数の関連した情報を1つの変数でまとめて管理したい場合に利用します。

配列を作成する

配列は、Arrayコンストラクター、リテラル表現のいずれかで作成できます。

以下のコードは、いずれも意味的に等価です。

// Arrayコンストラクター
let data1 = new Array('JavaScript','CoffeeScript','TypeScript');

// リテラル表現
let data2 = ['JavaScript','CoffeeScript','TypeScript'];

ただし、原則として、Arrayコンストラクターは利用すべきではありません。

リテラル表現のほうがシンプルに表現できるという理由もありますが、それ以上にバグの混入を防ぐという意味がります。

以下のコードを見てみましょう。

let data = new Array(5);

一見すると、「5という要素も持った配列」を定義しているようにも見えます。

しかし、Arrayコンストラクターは引数として単一の数値を受け取った場合、それを配列サイズと見なします。

上の例であれば、サイズ5の配列を生成します。

その性質上、以下のようなコードはエラーとなります。

let data = new Array(-5);

サイズ-5の配列を作成することはできないからです(-5という要素を持った配列を作るわけではありません)。

このように、Arrayコンストラクターは意味的にあいまいになりやすいことから、利用すべきではありません。

空の配列を作成する

空の配列は、以下のように空のブラケットで表現できます。

let data = [];

入れ子の配列を作する

配列の要素として配列を指定することで、入れ子の配列を作成することもできます。

複雑な配列では、以下の例のように、要素単位に改行を入れることで、構造を把握しやすくなります。

let data = [
   'C#',
   'Java',
   ['MySQL','PostgreSQL','SQLLite'],
];

上の例のように、末尾の要素はカンマで終えてもかまいません。

特に改行区切りで要素を列記しているような状況では、後から要素を追加する場合もカンマの追加漏れを防げます。

配列を参照する

作成した配列は、ブラケット構文で参照できます。

let data = [
   'C#',
   'Java',
   ['MySQL','PostgreSQL','SQLLite'],
];
console.log(data[1]);
// 結果:Java
console.log(data[2][2]);
// 結果:SQLLite

ブラケット([...])には、取得したい配列のインデックス番号(添え字)を指定します。

先頭の要素は(1ではなく)0なので注意してください。

入れ子の配列を参照する場合は、ブラケットも複数列記します。

連想配列(object)

インデックス番号でアクセスできる配列(通常配列)に対して、連想配列とは、名前をキーにアクセスできる配列です。

ハッシュともいいます。

辞書のように、意味あるキーで値を引けるので、データの視認性が高いという特徴があります。

連想配列は、リテラル表現Objectコンストラクターのいずれかで作成できます。

リテラル表現では(配列の「[...]」ではなく)「{...}」を利用する点に注意してください。

// リテラル表現
let obj1 = {a: 100, b: 500, c: 800}

// Objectコンストラクター
let obj2 = new Object();
obj2.a = 100;
obj2.b = 500;
obj2.c = 800;

console.log(obj1.b);
// 結果:500
console.log(obj1['b']);
// 結果:500

上記のコードのリテラル表現、Objectコンストラクター は、いずれも意味的に等価です。

ただし、冗長であるコンストラクター構文を利用する意味はないため、原則として、リテラル表現を優先して利用します。

値を参照するには、ドット演算子(console.log(obj1.b);)、ブラケット構文(console.log(obj1['b']);)のいずれかを利用します。

そして、こちらは微妙に意味が異なります。

というのも、ドット演算子で利用できるキーは識別子のみです。

つまり、「obj.987」のような表記はエラーです。(識別子の1文字目を数字にすることはできないからです。)

しかし、ブラケット構文ではキーを文字列として指定するので、「obj['987']」のような表記も正しく認識できます。

MEMO
連想配列とオブジェクトは同一のもの
JavaScriptの世界では、連想配列はオブジェクトです。

正確にいえば、Objectオブジェクトを連想配列として利用しています。

JavaScriptでは、文脈(用途)によって、同じものをオブジェクト、連想配列/ハッシュと異なる呼び方をします。

同じ理由から、連想配列のキーのことも、文脈ではプロパティと呼ぶこともあります。

さらに、値として関数リテラルを持つプロパティのことをメソッドと呼ぶ場合もあります。
MEMO
専用の連想配列
ES2015以降では、連想配列を扱うための専用の仕組みとして、Mapオブジェクトが提供されています(Mapオブジェクトについて詳しくは、【マップの使い方をマスターしよう!】をご覧ください)。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です