JavaScript 入門

はじめに

本ページは、JavaScript を使ってこれからプログラミングの学習を開始される方を対象に JavaScript の仕様をわかりやすく網羅的に解説した入門ページです。JavaScript の基本的な部分から細かく複雑な部分について、サンプルコードを混じえながら説明します。

変数

プログラムにおいて数値や文字列などの値は と呼ばれ、基本的な概念となります。それらの値を格納するものが 変数 です。変数の型には、基本型オブジェクト型 の 2 種類あります。基本型には、数値、文字列、論理値などがあります。数値、文字列、論理値の例は以下の通りです。

// 変数 num に数値 10 を格納
var num = 10;

// 変数 str に文字列 Hello を格納
var str = "Hello";

// 変数 bool に論理値 true を格納
var bool = true;
変数に値を格納する例

基本型には、特殊な値である nullundefined も含まれます。この値は、数値でも文字列でも論理値でもない特別な値です。オブジェクト型は、数値、文字列、論理値、nullundefined 以外の値となります。オブジェクト型にも特殊な例があり、グローバルオブジェクト、配列、関数などがあります。

数値

JavaScript では、整数と浮動小数点を区別せず、すべて浮動小数点で表します。

浮動小数点の最大値は ± 1.7976931348623157 × 10308 で、最小値は ± 5 × 10-324 となります。数値形式では、-9007199254740992 (-253) ~ 9007199254740992 (253) までの整数は正確に表せます。ただし、この範囲外の整数を扱う場合は精度が損なわれます。このようなプログラム内の数字を 数値リテラル と呼びます。数値リテラルには、整数リテラル浮動小数点リテラル があります。

整数リテラル

整数リテラルは、10 進数を指定します。リテラルの先頭に 0x または 0X を付けると 16 進数を指定できます。リテラルの先頭に 0 を付けると 8 進数の指定になりますが strictモード や JavaScript の処理系によってサポートされていない場合があるため、非推奨となります。

// 10 進数の整数リテラルを格納
var num10 = 123;

// 16 進数の整数リテラルを格納
var num16 = 0xff;

//  8 進数の整数リテラルを格納(非推奨)
var num8 = 0123;
整数リテラルを格納する例

浮動小数点リテラル

浮動小数点リテラルでは小数部や指数も使用できます。浮動小数点リテラルは、先頭が整数部、その次に小数点、その後ろに小数部の形式で記述します。指数は、先頭が英字の e または E、その次に + または - 記号 (省略可能)、その次に整数指数の形式で、小数部の後ろに記述します。浮動小数点リテラルの値は、先行する整数部と小数部の数値に 10 を指数部で指定された回数だけ掛けた値になります。

// 浮動小数点リテラルの書式
[数値][.数値][(E|e)[(+|-)]数値]

// 小数の浮動小数点リテラルを格納
var decimal = 123.456;

// 指数の浮動小数点リテラルを格納
var light = 2.99792458e8; // 2.99792458 × 108
浮動小数点リテラルを格納する例

算術演算 - Math関数

JavaScript の算術演算子は 加算(+)、減算(-)、乗算(*)、除算(/)、余剰(%) があります。これらの基本的な算術演算子以外にも、数学的な定数と関数を提供する Math オブジェクトが組み込まれています。

// プロパティ
Math.E            // ネイピア数			実行結果:
Math.LN2          // 2 の自然対数		実行結果:
Math.LN10         // 10の自然対数		実行結果:
Math.LOG2E        // 2 を底とした E の対数	実行結果:
Math.LOG10E       // 10を底とした E の対数	実行結果:
Math.PI           // 円周率			実行結果:
Math.SQRT1_2      // 1/2 の平方根		実行結果:
Math.SQRT2        // 2 の平方根			実行結果:

// メソッド
Math.abs(-123)    // 絶対値			実行結果:
Math.acos(0.5)    // 逆余弦			実行結果:
Math.asin(0.5)    // 逆正弦			実行結果:
Math.atan(0.5)    // 逆正接			実行結果:
Math.atan2(45,90) // 比率での逆正接		実行結果:
Math.ceil(0.5)    // 整数に切り上げ		実行結果:
Math.cos(0.5)     // 余弦			実行結果:
Math.exp(2)       // ネイピア数のべき乗		実行結果:
Math.floor(0.5)   // 整数に切り下げ		実行結果:
Math.log(2)       // 自然対数(底はe)		実行結果:
Math.max(1,0,-1)  // 最大の数			実行結果:
Math.min(1,0,-1)  // 最小の数			実行結果:
Math.pow(2,3)     // 23			実行結果:
Math.random()     // 0以上1未満の擬似乱数	実行結果:
Math.sin(0.5)     // 正弦			実行結果:
Math.sqrt(2)      // 平方根			実行結果:
Math.tan(0.5)     // 正接			実行結果:
JavaScript の Math 関数

浮動小数点の限界値

JavaScript ではオーバーフローやアンダーフローが発生した場合や、0 除算を行ってもエラーが発生しません。これは、多くのプログラミング言語の仕様と異なるものです。JavaScript では無限大を表現する Infinity や、負数の 0 を表す -0 や、数値に変換できない NaN(Not a Number) の特殊なグローバル変数が定義されているためです。

Infinity は絶対値で表現可能な最大値を超えるオーバーフローが発生した場合に出力されます。Infinity は加算、減算、乗算、除算を行っても変化しませんが、正負の符号は変化する場合があります。正の無限大と負の無限大を比較した場合、異なる値と判定されます。

-0 は負の値に対して絶対値で表現可能な最小値を超えるアンダーフローが発生した場合に出力されます。正の値に対するアンダーフローが発生した場合は、通常の 0 を返します。しかし、-00 を比較した場合、等しいと判定されるため、プログラミングで意識する必要はありません。

NaN は 0 を 0 で除算した場合や、無限大を無限大で除算した場合や、負数の平方根を求めた場合に出力されます。その他、数値に変換できない算術演算を行った場合も NaN が出力されます。NaN は特殊な値であり、どの値と比較しても等しいと判定されません。NaNNaN を比較しても等しいと判定されないため、注意が必要です。NaN であるか判定するためには、isNaN() 関数や isFinite() 関数を使用します。

浮動小数点の丸め誤差

プログラミング言語で使われている IEEE 754 浮動小数点表現形式は、2 進数表記であるため 10 進数を正確に表現できない場合があります。JavaScript でも近似表現となるため、丸め誤差の問題が発生します。

JavaScript の数値の精度は十分高いので、0.1 をほぼ近似表現できます。しかし、数値を正確に表現できないために、問題が生じる場合もあります。

var x = 0.3 - 0.2;
var y = 0.2 - 0.1;

x==y;   // false
x==0.1; // false
y==0.1; // true
丸め誤差が発生するケース

丸め誤差のために、0.3 と 0.2 の差分の近似表現と、0.2 と 0.1 の差分の近似表現は正確には同じ値になりません。ただし、この問題は JavaScript 特有の問題ではありません。2 進浮動小数点数値を使うプログラミング言語なら同じ問題が発生します。また、先ほど紹介したコードの x と y の値はほぼ同じで、両方とも正しい値に 非常に近い値 になっています。等しいかどうかを比較しようとしたときに問題が生じます。

日付と時刻

JavaScript には、日付と時刻を表現するオブジェクトを生成するために、Date() コンストラクタが用意されています。Date オブジェクトには、日付計算を行うための API がメソッド形式で提供されています。

var then    = new Date(2010,0,1);          // 2010年1月1日。
var later   = new Date(2010,0,1,17,10,30); // 2010年1月1日午後5:10:30。
var now     = new Date();                  // 現在の日付と時刻。
var elapsed = now - then;                  // Date 型の減算。ミリ秒単位の間隔。

later.getFullYear()        // 2010
later.getMonth()           // 0: 1月を0とする月番号。
later.getDate()            // 1: 1日を1とする日番号。
later.getDay()             // 5: 曜日。0が日曜日、5が金曜日。
later.getHours()           // 17: 現地時間午後5 時。
later.getUTCHours()        // UTC での時間。タイムゾーンに依存する。
later.toString()           // "Fri Jan 01 2010 17:10:30 GMT-0800 (PST)"
later.toUTCString()        // "Sat, 02 Jan 2010 01:10:30 GMT"
later.toLocaleDateString() // "01/01/2010"
later.toLocaleTimeString() // "05:10:30 PM"
later.toISOString()        // "2010-01-02T01:10:30.000Z": ES5 のみ。
日付と時刻

テキスト

文字列は Unicode の文字コードで表現される 16 ビット (U+0000 ~ U+FFFF) のデータの集合体です。文字列は位置情報を保有しており、配列と同様に最初の文字には 0 がインデックスされています。ただし、文字列の長さは 1 からカウントします。空文字列は、長さが 0 の文字列となります。また、Javascript には C 言語の Char 型のように 1 文字の文字列を表す型は用意されていません。

//   0   1   2   3   4 :インデックス
//   1   2   3   4   5 :文字列の長さ
//  [H] [e] [l] [l] [o]
var str = "Hello";

str[1];     // e を返す
str.length; // 5 を返す
インデックスと文字列の長さ

文字列リテラル

JavaScript で文字列リテラルを表現する場合は、' または " で文字列を囲みます。文字列の中に '" を含む場合は、種類の違う引用符を使うか、エスケープを行う必要があります。

"It always seems impossible until it’s done.";
'何事も成功するまでは不可能に思えるものである。- Nelson Mandela';
JavaScript の文字列リテラル

多くの場合、文字列リテラルは単一行で表現されますが、ECMAScript 5からは複数行に分割できます。複数行に分割する場合は、各行の末尾に \ を付けると分割できます。

"It always \
seems impossible \
until it’s done.";
文字列リテラルを複数行に分割する

文字列リテラルを複数行に分割しても、実態は改行されていない点に注意して下さい。もしも文字列リテラルに改行コードを含める場合は \n を記述します。詳細は、後述するエスケープシーケンスを参照して下さい。

エスケープシーケンス

文字列リテラルをエスケープする場合は、対象の文字の直前にバックスラッシュ \ を付けます。下記の例では、' で囲まれた中に ' を含んでいますが、\ を直前に付けると、文字列リテラルを囲む記号からただの文字としてエスケープしています。\ をエスケープする場合は、\\ のようにします。

'It always seems impossible until it\’s done.';
文字列リテラルのエスケープ

文字以外にも制御文字などもエスケープシーケンスを用いて表現が可能です。

JavaScript のエスケープシーケンス
シーケンス意味
\0NULL文字(\u0000)
\bバックスペース(\u0008)
\t水平タブ(\u0009)
\n改行(\u000A)
\v垂直タブ(\u000B)
\f改ページ(\u000C)
\r復帰(\u000D)
\"二重引用符(\u0022)
\'単一引用符(\u0027)
\\バックスラッシュ(\u005C)
\xXX2桁の16進数XXで指定する Latin-1 文字
\uXXXX4桁の16進数XXXXで指定する Unicode 文字

文字列の操作

JavaScript には文字列を操作する様々な機能が備わっています。

var str = "Hello, " + "world"; // +演算子による文字列連結	実行結果:
str.[0];                       // 最初の文字 			実行結果:
str.[str.length-1];            // 最後の文字 			実行結果:
str.charAt(0);                 // 最初の文字 			実行結果:
str.charAt(str.length-1);      // 最後の文字 			実行結果:
str.substring(0,5);            // 最初の文字から5文字 		実行結果:
str.slice(0,5);                // 最初の文字から5文字 		実行結果:
str.slice(-5);                 // 最後の文字から5文字 		実行結果:
str.indexOf("l");              // 最初のlが出現する位置 	実行結果:
str.lastIndexOf("l");          // 最後のlが出現する位置 	実行結果:
str.split(", ");               // ", "で文字列を分割する 	実行結果:
str.replace("H","h");          // "H"を"h"に変換する 		実行結果:
str.toLowerCase();             // 小文字に変換する 		実行結果:
str.toUpperCase();             // 大文字に変換する 		実行結果:
文字列の操作

"replace()" や "toLowerCase()" などの文字列のメソッドを使用しても、元の文字列は変更されません。

パターンマッチング

JavaScript には、テキストパターンを表すオブジェクトを生成するために、RegExp() コンストラクタが定義されています。このパターンは、正規表現を使って記述します。JavaScript では、正規表現の構文として Perl の構文を使用しています。文字列と RegExp オブジェクトの両方で、正規表現を使ったパターンマッチングや検索置換処理を行うためのメソッドが用意されています。

RegExp は、JavaScript の基本的な型ではありません。Date オブジェクトと同様に、便利な API が用意されたオブジェクトの一種です。正規表現の文法は複雑で、API も簡単ではありません。

RegExp は基本的な型ではありませんが、JavaScript プログラム中に直接記述するためのリテラル形式の文法が用意されています。テキストをスラッシュで囲むと、正規表現リテラルを記述できます。末尾のスラッシュの後ろには、さらに文字を記述できます。この文字で、パターンの意味を変更できます。

/^HTML/           // 文字列の先頭の HTML の文字列にマッチ。
/[1-9][0-9]/      // 0 以外の数字の後に続く任意の数字にマッチ。
/\bjavascript\b/i // 大文字小文字区別なしに javascript の文字列にマッチ。
パターンマッチング

RegExp オブジェクトには、便利なメソッドがたくさん用意されています。また、文字列にも、RegExp を引数として利用するメソッドが用意されています。

var text = "testing: 1, 2, 3"; // 例となるテキスト。
var pattern = /\d+/g           // 1 個以上の数字列にすべてマッチ。

pattern.test(text)             // true: マッチする部分が存在する。
text.search(pattern)           // 9: 最初にマッチした文字の位置。
text.match(pattern)            // ["1", "2", "3"]: すべてのマッチを含む配列。
text.replace(pattern, "#");    // "testing: #, #, #"
text.split(/\D+/);             // ["","1","2","3"]: 数字以外の文字で配列に分割。
RegExp を引数として利用するメソッド

論理値

論理値とは 0 か 1 かを表す値です。その他にも ON と OFF、YES と NO、真と偽などで表現されます。プログラムには様々な条件が登場しますが、どの条件も最終的には 0 か 1 かのどちらかに評価され、中間的な値はありません。プログラム上では 0 を false、1 を true の予約語として定義されています。

var num = 10;

num == 10; // 論理値は true
num == 12; // 論理値は false
論理値

上記の例では、数値の比較により論理値を返しています。正しければ true、誤っていれば false を返します。多くのプログラミング言語で論理値を使うケースは if/else 文による条件判定です。条件判定により true ならばある処理を実行し、false ならば別の処理を実行するケースです。

var num = 10;

if(num == 10){
  document.write("num の値は 10 です。");
}
else{
  document.write("num の値は 10 ではありません。");
}
論理値による条件判定

上記の例では、比較演算子による条件判定を行っていますが、JavaScript では Infinityundefined などを含むすべての値は truefalse に変換できます。以下の値は、すべて false に変換されるため、false と同じ動作をします。逆に以下の値以外は、すべてのオブジェクト (配列) も含めて true に変換され、true と同じ動作をします。

  • undefined
  • null
  • 0
  • -0
  • NaN
  • "" (空文字)

例えば、変数 obj にはオブジェクトか null 値が格納されているとします。変数 obj が null 値 でないことは、if 文を使って明示的にテストできます。

if(obj !== null) ...

非同値演算子 (!==) は、変数 obj と null 値を比較して、true または false のどちらかに評価します。しかし、nullfalse に変換され、オブジェクトは true に変換される規則を使えば、以下のように評価式を省略できます。

if(obj) ...

ただし、上記の 2 つの評価式は厳密には同じではありません。前者の評価式では、厳密に null 値であるかを評価していますが、後者の評価式では null 値を含め undefined や 空文字の場合、つまり false に変換される値の場合はすべて if 文を実行しません。どちらの if 文が適切かは、変数 obj に代入される値によります。null 値であるかどうかを、0 や "" であるかどうかを区別する場合は、明示的に比較を行って下さい。

論理 AND, OR, NOT 演算

&& 演算子は、論理 AND 演算を行います。両方のオペランド (被演算子) が true に変換される値の場合にのみ、true と評価します。それ以外は false と評価します。右辺のオペランド A と 左辺のオペランド B の論理 AND 演算は以下の通りです。

右辺 A   左辺 B   結果
---------------------
true    true    true
true    false   false
false   true    false
false   false   false
論理 AND 演算

|| 演算子は、論理 OR 演算を行います。どちらかのオペランドが true に変換される値であれば、true と評価します。両方のオペランドが false に変換される値であれば、false と評価します。右辺のオペランド A と 左辺のオペランド B の論理 OR 演算は以下の通りです。

右辺 A   左辺 B   結果
---------------------
true    true    true
true    false   true
false   true    true
false   false   false
論理 OR 演算

3 つ目の演算子は、単項 ! 演算子です。! 演算子は論理 NOT 演算を行います。オペランドが false に変換される値であれば、true と評価します。逆に、オペランドが true に変換される値であれば、false と評価します。

if ((x == 0 && y == 0) || !(z == 0)) {
// x と y の両者が 0、または z が 0 以外の場合。
}
論理値による条件判定

null と undefined

JavaScript 言語の null 値は、値が存在しないことを表す特殊な値です。null に対して typeof 演算子を使うと、object の文字列が返されます。つまり、null は、オブジェクトが存在しない特別なオブジェクト値となります。

しかし、nullnull のオブジェクト型の唯一のメンバーと見なされており、オブジェクトだけでなく、数値や文字列に対しても値がないことを示すために使われます。ほとんどのプログラミング言語に、JavaScript の null に相当する nulnil などが用意されています。

JavaScript には、値がないことを表す値として未定義 (undefined) 値があります。未定義 (undefined) 値は、初期化されていない変数の値や、存在しないオブジェクトプロパティや配列の要素を読み出したときの値が未定義値です。戻り値のない関数が返す値や、関数の呼び出し時に値が指定されなかった引数の値も未定義値となります。

undefined は、あらかじめ定義されたグローバル変数で、未定義値に初期化されています。null とは異なり予約語ではありません。ECMAScript 3 では、undefined は読み書き可能な変数ですので、値を変更できます。この問題は、ECMAScript 5 で修正され、undefined は読み出しのみになっています。未定義値に対して typeof 演算子を使うと、未定義値を表す undefined が返されます。

typeof null;       // object
typeof undefined;  // undefined
null と undefined の typeof の結果

このような違いがあるにもかかわらず、nullundefined は両者とも値がないので、同一のものとして扱えます。等値演算子 (==) は両者を等しいと判定します。厳密に両者を区別したいときは、同値演算子 (===) を使って下さい。

undefined == null;   // true
undefined === null;  // false
null と undefined の比較

nullundefined もプロパティやメソッドを持っていません。実際に . や [] を使ってプロパティやメソッドにアクセスしようとすると TypeError が発生します。undefined はシステムレベルで予期せぬ、ある意味エラーのような場合を表すもので、null はプログラムレベルで予定どおりの場合を表すものと考えてもよいでしょう。両者のどちらかを変数やプロパティに値を設定したり、関数に値を渡したりしたい場合には、null を使うようにしてください。

グローバルオブジェクト

グローバルオブジェクトは通常のオブジェクトですが、重要な役割を果たします。グローバルオブジェクトのプロパティは、グローバルに定義された名前となり、プログラムのどこからでも利用できます。JavaScript インタプリタが起動したとき、新しいグローバルオブジェクトが生成され、以下のようなプロパティ群を持つように初期化されます。

  • グローバルプロパティ:undefinedInfinityNaN
  • グローバル関数:isNaN()parseInt()eval()
  • コンストラクタ関数:Date()RegExp()String()Object()Array()
  • グローバルオブジェクト:Math、JSON

グローバルオブジェクトのプロパティ (undefinedInfinityNaN) は予約語ではありませんが、多くの場合、予約語のように扱われます。特に複数人のチームでコーディングを行っている場合、これらのグローバルプロパティを勝手に変更すると、混乱のもとになります。

JavaScript では、Window オブジェクトにグローバルプロパティやグローバル関数などが定義されています。トップレベルコード (関数の一部ではない JavaScript コード) では、this キーワードを使ってグローバルオブジェクトを参照できます。

var global = this; // グローバルオブジェクトを参照する変数 global を定義する。
this キーワード

JavaScript では、ブラウザウィンドウを表す Window オブジェクトがグローバルオブジェクトの役目を果たします。グローバル Window オブジェクトには、自身を参照する window プロパティが用意されています。グローバルオブジェクトを参照するときに this の代わりに、window プロパティを使用できます。Window オブジェクトでは、コア言語用のグローバルプロパティのほかに、Web ブラウザやクライアントサイド JavaScript 用のグローバルプロパティが定義されています。

// すべて同じ結果を返します。
window.document.write('Hello!');
this.document.write('Hello!');
document.write('Hello!');
Window オブジェクトの document プロパティへのアクセス

グローバルオブジェクトが最初に生成されたときに、まず JavaScript でのグローバル値が設定されます。このほかにも、この特殊なグローバルオブジェクトには、プログラムで定義したグローバル値も保持されます。つまり、コード中でグローバル変数を宣言した場合、その変数はグローバルオブジェクトのプロパティになります。

ラッパーオブジェクト

JavaScript のオブジェクトは複合型の値です。つまり、プロパティ(名前付きの値) の集合体です。プロパティの値を参照するときは、"." を使って表記します。プロパティが関数の場合は、メソッドと呼びます。オブジェクト obj のメソッド mtd を呼び出すときは、obj.mtd() と記述します。文字列はオブジェクトではありませんが、プロパティとメソッドを持ちます。

var s = "hello world!"; // 文字列を定義
var word = s.substring(s.indexOf(" ")+1, s.length); // 文字列のプロパティを使用

文字列 s のプロパティを参照すると、文字列値をオブジェクトに変換します。new String(s) と同じ変換をします。このオブジェクトは、文字列用のメソッドを継承するので、プロパティを参照できます。

文字列と同じような理由で、数値や論理値もメソッドを持ちます。一時的なオブジェクトが、Number() コンストラクタや Boolean() コンストラクタを使って生成されます。null 値や undefined 値には、一時的なオブジェクトは存在しません。したがって、null 値や undefined 値のプロパティにアクセスしようとすると、TypeError が発生します。

var s = "test"; // 文字列値のサンプル。
s.len = 4;      // 文字列に対してプロパティを設定する。
var t = s.len;  // 設定したプロパティを読み出す。

このコードを実行すると t の値は undefined になります。2 行目は、一時的に String オブジェクトが生成され、このオブジェクトの len プロパティに 4 を設定しています。しかし、このオブジェクトは廃棄されます。3 行目は、元の (変更されていない) 文字列値から新たに String オブジェクトが生成され、len プロパティを読み出します。このプロパティは存在しないので、読み出した値は undefined になります。このコード例からわかるように文字列、数値、論理値は、プロパティの値を読み出すとオブジェクトのように振る舞います。しかし、プロパティに値を設定する場合は、単に無視されるだけです。値の変更は、一時的に生成されるオブジェクトに対して行われ、すぐに捨てられてしまうからです。

このように、文字列や数値、論理値のプロパティにアクセスするときに生成される一時的なオブジェクトを ラッパーオブジェクト と呼びます。文字列、数値、論理値は、通常のオブジェクトと異なり、プロパティは読み出しのみであり、新たなプロパティも定義できません。

不変な基本型値と可変なオブジェクト参照

JavaScript では、基本型値 (undefinednull、論理値、数値、文字列) とオブジェクト (配列、関数) では、基本的に異なります。

基本型は基本型値を変更する方法がないため不変です。文字列は文字の配列のように考えることができるため、指定した場所の文字を変更できるのではないかと思うかもしれません。しかし、JavaScript では、このような処理はできません。文字列を変更するように見えるメソッドはすべて、文字列を変更するのではなく、新たな文字列を返しているためです。

var s = "hello"; // 小文字の文字列から始める。
s.toUpperCase(); // "HELLO" が返されるが、s が変更されたわけではない。
s                // "hello" が返される。元の文字列は変更されていない。

また、基本型は値で比較します。2 つの基本型値が同じ値を持つ場合にのみ、2 つの値は等しいと判定されます。数値、論理値、nullundefined の場合は問題ありませんが、文字列の場合はやや異なります。2 つの異なる文字列値を比較する場合、両者が同じ長さで、各インデックスの文字が同じ場合にのみ、2 つの文字列値は等しいと判定します。これらの不変な基本型に対して、オブジェクトは可変であり、値を変更できます。

var o = { x:1 }; // オブジェクトは可変。
o.x   = 2;       // プロパティの値を変更して、オブジェクトを更新する。
o.y   = 3;       // 新しいプロパティを追加して、さらに更新する。

var a = [1,2,3]  // 配列も可変。
a[0]  = 0;       // 配列の要素の値を変更する。
a[3]  = 4;       // 配列に新しい要素を追加する。

オブジェクトの比較は値では行いません。同じプロパティを持ち、プロパティの値が同じだったとしても、異なる 2 つのオブジェクトは等しいとは判定されません。同様に、同じ要素を同じ順序で持つ配列も等しいとは判定されません。

var o = {x:1}, p = {x:1}; // 同じプロパティを持つ 2 つのオブジェクト。
o === p                   // => false: 別々のオブジェクトは等しいと判定されない。

var a = [], b = [];       // 2 つの別々の空の配列。
a === b                   // => false: 別々の配列は等しいと判定されない。

オブジェクトは基本型と区別するために、参照型と呼ばれる場合もあります。2 つのオブジェクト値は、両方が同じオブジェクトを参照している場合のみ同一と判定されます。

var a = []; // 空の配列を参照する変数 a。
var b = a;  // b は a の配列を参照する。

b[0] = 1;   // 変数 b を使って配列を参照し更新する。
a[0]        // 実行結果は 1 になる。

a === b     // => true: a と b は同じオブジェクトを参照するので、等しいと判定される。

上記の例では、変数にオブジェクト (配列) を代入することは、単に参照を代入しているだけです。オブジェクトの新たなコピーを作成するわけではありません。オブジェクトや配列の新たなコピーを作成したい場合は、オブジェクトのプロパティや、配列の要素を明示的にコピーしなければなりません。以下の例では、for ループを使ってコピーしています。

var a = ['a','b','c'];              // コピーしたい配列。
var b = [];                         // コピー先になる別の配列。
for(var i = 0; i < a.length; i++) { // a[] のインデックスごとに、
  b[i] = a[i];                      // a の要素をb にコピーする。
}

同様に、2 つのオブジェクトや配列を比較したいのであれば、プロパティや要素を比較する必要があります。以下のコードでは、2 つの配列を比較する関数を定義しています。

function equalArrays(a,b) {
  if (a.length != b.length) return false; // 大きさの異なる配列は等しくない。
  for(var i = 0; i < a.length; i++)       // すべての要素を巡回する。
    if (a[i] !== b[i]) return false;      // 1 つでも違っていれば、等しくない。
  return true;                            // すべて同じであれば、等しい。
}

型の変換

JavaScript の型変換は、非常に柔軟な対応を行います。例えば、文字列への型変換が必要であると判断した場合は、どのような値でも文字列への変換を試みます。数値への型変換が必要であると判断した場合は、数値への変換を試みます。(変換できない場合には、NaN に変換します)。

10 + " objects"  // "10 objects" : 数値の 10 が文字列に変換される。
"7" * "4"        // 28 : 両方の文字列が数値に変換される。
var n = 1 - "x"; // NaN : 文字列 "x" は数値に変換できない。
n + " objects"   // "NaN objects" : NaN は "NaN" の文字列に変換される。

JavaScript での型変換について、下表に示します。

JavaScript での型変換
文字列数値論理値オブジェクト
undefined
null
"undefined"
"null"
NaN
0
false
false
TypeError 例外
TypeError 例外
""
"1.2"
"one"
1
1.2
NaN
false
true
true
new String("")
new String("1.2")
new String("one")
0
-0
NaN
Infinity
-Infinity
1
"0"
"0"
"NaN"
"Infinity"
"-Infinity"
"1"
false
false
false
true
true
true
new Number(0)
new Number(-0)
new Number(NaN)
new Number(Infinity)
new Number(-Infinity)
new Number(1)
true
false
"true"
"false"
1
0
new Boolean(true)
new Boolean(false)
{}
[]
[9]
['a']
function(){}
(後述)
""
"9"
join() を使う
(後述)
(後述)
0
9
NaN
NaN
true
true
true
true
true

上表の通り、基本型から基本型への変換は理解しやすいと思います。ただし、文字列から数値への変換は、注意が必要です。数値としての文字列を含む場合、その文字列が数値リテラルに変換されます。数値リテラルとして使えない文字列が含まれる場合は、NaN が返されます。

基本型からオブジェクトへの変換も比較的理解しやすいと思います。基本型値は、String()Number()Boolean() コンストラクタを明示的に呼び出したかのように、ラッパーオブジェクトに変換されます。ただし、nullundefined は例外で、変換は行われず TypeError 例外が発生します。

オブジェクトから基本型への変換は、後述する "オブジェクトから基本型への変換" で説明します。

変換と比較

JavaScript では、柔軟に型変換が行われますが、等値演算子 (==) でも、等しいかどうかの判定が柔軟に行われます。例えば、以下に挙げる比較はすべて true と判定されます。

null == undefined // この2 つの値は等しいものとして扱われる。
"0" == 0          // 比較する前に、文字列は数値に変換される。
0 == false        // 比較する前に、論理値は数値に変換される。
"0" == false      // 比較する前に、両方のオペランドが数値に変換される。

ある値が型変換によって別の値になる場合、2 つの値が等しい値ではない点に注意して下さい。例えば、undefined が論理値に型変換が必要な場合は、false に変換されます。しかし、これは undefined == false の意味ではありません。

if 文の場合は論理値型が必要になるため undefinedfalse に変換します。しかし、== 演算子では、論理値型を必要としないため、オペランドを論理値に変換しません。

明示的な型変換

型変換は通常、自動的に行われますが、明示的に型変換を行わなければならない場合もあります。明示的に型変換を行うには、Boolean()Number()String()Object() 関数を使用するのが一般的です。これらの関数は、ラッパーオブジェクトのコンストラクタです。しかし、new 演算子を省略して呼び出すと、型変換を行う関数として動作します。

Number("3")   // 3
String(false) // "false" (false.toString() でも変換可能)
Boolean([])   // true
Object(3)     // new Number(3)

nullundefined 以外は、toString() メソッドを持ちます。一般的に、toString() メソッドの戻り値は、String() 関数の戻り値と同じになります。また、nullundefined をオブジェクトに変換しようとすると、TypeError 例外が発生します。しかし、Object() 関数を使う場合、TypeError 例外は発生せず、空オブジェクトを新たに生成して返します。

JavaScript の演算子には、暗黙に型変換を行うものがあります。+ 演算子の一方のオペランドが文字列の場合、もう一方のオペランドは文字列に変換され、文字列と文字列の連結を試みます。単項 + 演算子は、オペランドを数値に変換します。単項 ! 演算子は、オペランドを論理値に変換し、真偽を反転させます。

x + "" // String(x) と同じ。
+x     // Number(x) と同じ。x-0 の表現も使われる。
!!x    // Boolean(x) と同じ。! を 2 回記述している点に注意。

JavaScript には、数値から文字列への変換や、文字列から数値への変換において、基数を指定できるメソッドが用意されています。Number クラスに定義されている toString() メソッドには、変換対象の基数を 2 進数から 36 進数までを引数で指定できます。引数を指定しなかった場合は、10 進数に変換されます。

var n = 17;
binary_string = n.toString(2);      // "10001"
octal_string = "0" + n.toString(8); // "021"
hex_string = "0x" + n.toString(16); // "0x11"

Number クラスには、数値を文字列に変換するメソッドとして、toFixed()toExponential()toPrecision() の 3 つが定義されています。3 つのメソッドで数値を文字列へ変換すると、必要に応じて四捨五入や末尾への 0 追加が行われます。

toFixed() メソッドは、数値が文字列に変換され、小数点以下は指定した桁数だけ残されます。このメソッドでは指数表現には変換されません。

toExponential() メソッドは、数値は指数表現に変換されます。この指数表現では、整数部が 1 桁で、小数点以下に指定された桁数を持つように変換されます。

toPrecision() メソッドは、有効桁数を指定して数値を文字列に変換できます。有効桁数が数値の整数部よりも桁数が少ない場合には、指数表現に変換されます。

var n = 123456.789;

n.toFixed(0);       // "123457"
n.toFixed(2);       // "123456.79"
n.toFixed(5);       // "123456.78900"

n.toExponential(1); // "1.2e+5"
n.toExponential(3); // "1.235e+5"

n.toPrecision(4);   // "1.235e+5"
n.toPrecision(7);   // "123456.8"
n.toPrecision(10);  // "123456.7890"

Number() 型変換関数に対して文字列を引数として指定すると、文字列を整数リテラル、または浮動小数点リテラルとして解釈します。この関数は 10 進数しか処理できません。また、文字列の末尾に数値リテラルとして利用できない文字が含まれている場合は解釈できません。Number() 型変換関数と似ている関数として、parseInt() 関数と parseFloat() 関数があります。これらの関数は、Number() 関数より柔軟に対応します。

parseInt() 関数は整数のみ、parseFloat() 関数は整数と浮動小数点数を解釈します。先頭が 0x または 0X の場合、parseInt() 関数はその数値を 16 進数と解釈します。parseInt()parseFloat() 関数も、できるだけ多くの数値文字列を解釈しようとします。文字が数値として解釈できない文字の場合には、NaN が返されます。

parseInt("3 blind mice")   // 3
parseFloat(" 3.14 meters") // 3.14
parseInt("-12.34")         // -12
parseInt("0xFF")           // 255
parseInt("0xff")           // 255
parseInt("-0XFF")          // -255
parseFloat(".1")           // 0.1
parseInt("0.1")            // 0
parseInt(".1")             // NaN (整数の場合 "." が先頭にくることはない)
parseFloat("$72.47");      // NaN (数値は "$" が先頭にくることはない)

parseInt() 関数は、2 番目の引数として 2 (進数) から 36 (進数) までの基数を指定できます。

parseInt("11", 2);   // 3
parseInt("ff", 16);  // 255
parseInt("zz", 36);  // 1295
parseInt("077", 8);  // 63
parseInt("077", 10); // 77

オブジェクトから基本型への変換

すべてのオブジェクトは、配列や、関数、ラッパーオブジェクトも含めて true に変換されます。オブジェクトから文字列への変換や、オブジェクトから数値への変換は、変換対象のオブジェクトのメソッドを呼び出すことで行います。オブジェクトには、変換を行うための toString() メソッドと valueOf() メソッドがあります。

toString() メソッドは、オブジェクトを表す文字列を返します。

({x:1, y:2}).toString() // "[object Object]"

多くのクラスには toString() メソッドが用意されています。例えば、以下のようなものがあります。

  • Array クラスの toString() メソッドは、配列の各要素をカンマで区切って結合した文字列を返します。
  • Function クラスの toString() メソッドは、処理系で定義されている関数表現を返します。
  • Date クラスの toString() メソッドは、日付・時刻文字列を返します。
  • RegExp クラスの toString() メソッドは、RegExp オブジェクトを文字列に変換します。
[1,2,3].toString()              // "1,2,3"
(function(x){f(x);}).toString() // "function(x) {\n f(x);\n}"
/\d+/g.toString()               // "/\\d+/g"
new Date(2010,0,1).toString()   // "Fri Jan 01 2010 00:00:00 GMT-0800 (PST)"

valueOf() メソッドは、toString() ほど明確に定義されていません。基本的には、オブジェクトを表す基本型値に変換するために用いられます。オブジェクトは複合型の値なので、1 つの基本型で表現できません。このため、デフォルトの valueOf() メソッドは、単純にオブジェクトを返します。

ラッパークラスの valueOf() メソッドは元の基本型値を返します。また、配列や関数、正規表現クラスは、デフォルトメソッドを継承しています。そのため、これらの型のインスタンスに対して valueOf() を使用すると、オブジェクトそのものが返されます。Date クラスの valueOf() メソッドは、日付を 1970 年 1 月 1 日からの時間をミリ秒単位で表したものを返します。

var d = new Date(2010, 0, 1); // 2010 年 1 月 1 日(太平洋標準時)
d.valueOf()                   // 1262332800000

オブジェクトを文字列に変換する場合、次の 3 つの手順を踏みます。

  • オブジェクトが toString() メソッドを持つ場合、toString() メソッドを呼び出します。基本型値が返されたら、その値を文字列に変換して返します。
  • オブジェクトに toString() メソッドが定義されていない場合や、基本型値が返されなかった場合、valueOf() メソッドが定義されているかを調べます。valueOf() メソッドが定義されている場合、valueOf() メソッドを呼び出します。戻り値が基本型値の場合、この値を文字列に変換して返します。
  • 上記以外の場合、TypeError 例外をスローします。

オブジェクトを数値に変換する場合、次の 3 つの手順を踏みます。文字列の手順と異なり valueOf() メソッドから利用している点に注意して下さい。

  • オブジェクトが valueOf() メソッドを持つ場合、valueOf() メソッドを呼び出します。基本型値を返されたら、その値を基本型値を数値に変換して返します。
  • オブジェクトに valueOf() メソッドが定義されていない場合や、基本型値が返されなかった場合、toString() メソッドが定義されているかを調べます。toString() メソッドが定義されている場合、基本型値を文字列に変換して返します。
  • 上記以外の場合、TypeError 例外をスローします。

配列は、valueOf() メソッドを継承するので、基本型値ではなくオブジェクトを返します。そのため、配列を数値に変換する場合は、toString() メソッドが使われます。空の配列の場合は、空の文字列に変換されます。そして、空の文字列であるため、数値の 0 に変換されます。

JavaScript の + 演算子は、数値の加算と、文字列の連結を行います。オペランドの一方がオブジェクトの場合、ほかの算術演算で利用するオブジェクトから数値への変換ではなく、オブジェクトから基本型への変換を行います。== 等値演算子の場合も同じです。オブジェクトを基本型値と比較する場合、オブジェクトから基本型への変換を行います。

+== のオブジェクトから基本型への変換では、Date オブジェクトの場合にも特殊な処理が行われます。Date クラスは、コアJavaScript 言語において、文字列や数値への意味のある変換が定義されている唯一のクラスです。オブジェクトから基本型への変換では、Date オブジェクト以外のオブジェクトについては、オブジェクトから数値への変換を行います。一方で、Date オブジェクトに対しては、オブジェクトから文字列への変換を使います。

< 演算子などの比較演算子でも、== と同様に、オブジェクトから基本型への変換が行われます。ただし、Date オブジェクトは特別な処理は行われません。オブジェクトはすべて、まず valueOf() を使って変換し、変換できない場合には toString() を使います。

+==!= と関係演算子のみ、文字列から基本型への変換が行われます。ほかの演算子では、もっと明示的に必要とされる型に変換します。また、Date オブジェクト用の特殊な処理も行われません。例えば、- 演算子はオペランドを数値に変換します。以下に、Date オブジェクトに対して、+-== を使ったときの振る舞いの例を示します。

var now = new Date(); // Date オブジェクトを生成する。

typeof (now + 1)      // "string": + は日付を文字列に変換する。
typeof (now - 1)      // "number": - はオブジェクトを数値型へ変換する。
now == now.toString() // true: 暗黙の文字列変換と、明示的な文字列変換。
now > (now -1)        // true: > はDate を数値に変換する。

変数の宣言

多くのプログラミング言語では、変数を利用する場所のできるだけ近くで宣言するようにして、スコープをできるだけ狭くすることが、良いプログラミングスタイルだと一般的に言われています。しかし、Javascript ではこのプログラミングスタイルは適用されません。ここでは変数の宣言の説明を行うため、詳細は「変数のスコープ」 を参照して下さい。

Javascript では変数を宣言するときはキーワード var を使います。

var i;
var sum;

以下に示すように、複数の変数をカンマ(,)で区切って宣言できます。

var i, sum;

いくつかの変数の宣言と初期値の設定をカンマで区切って指定できます。

var message = "hello";
var i = 0, j = 0, k = 0;

var 文で変数の初期値を設定しなかった場合は、宣言された変数は、何か値が代入されるまで undefined になります。なお、for ループや for/in ループで var 文を記述することで、ループの一部としてループ変数を宣言できます。

for(var i = 0; i < 10; i++) console.log(i);
for(var i = 0, j=10; i < 10; i++,j--) console.log(i*j);
for(var p in o) console.log(i);

C や Java のような型付きのプログラミング言語に慣れている方であれば、JavaScript の変数宣言では型が指定されていないことに気付いたと思います。JavaScript の変数は、任意の型の値を保持できます。例えば、ある変数に数値を代入した後、同じ変数に文字列を代入してもまったく問題ありません。

var i = 10;
i = "ten";

宣言の繰り返しと省略

var 文で同じ変数を繰り返し宣言しても問題はありません。var 文で初期値が設定されていた場合は、代入文と同じ役割を果たします。

var 文で宣言されていない変数から値を読み出した場合は、エラーになります。ECMAScript 5 の strict モードでは、宣言されていない変数に対して値を代入してもエラーになります。しかし、非 strict モードでは、宣言されていない変数に対して値を代入した場合、JavaScript はグローバルオブジェクトのプロパティとして変数を作成します。ただし、このような使い方は無用な混乱の元となるため非推奨です。そのため、変数は常に var を使って宣言するようにしてください。

変数のスコープ

プログラムのソースコード中における、変数の有効範囲を スコープ と言います。変数には、グローバル変数とローカル変数があり、それぞれスコープの範囲が異なります。グローバル変数のスコープは、プログラム全体になります。ローカル変数のスコープは、その変数が宣言された関数の中だけに限定されます。関数の引数もローカル変数になるため、引数のスコープは関数の中だけに限定されます。

関数の中にグローバル変数と同じ名前のローカル変数があった場合は、ローカル変数が優先されます。グローバル変数と同じ名前でローカル変数や引数を宣言すると、グローバル変数が無効になります。

var scope = "global";   // グローバル変数を宣言する。

function checkscope() {
  var scope = "local";  // 同じ名前でローカル変数を宣言する。
  return scope;         // ローカル変数の値が返される。グローバル変数の値ではない。
}

checkscope()            // "local"

グローバル変数の場合は var 文を省略できますが、ローカル変数は var 文で宣言しなければなりません。var 文を省略すると、次のようになります。

scope = "global";          // var 文を省略してグローバル変数を宣言する。

function checkscope2() {
  scope = "local";         // var を省略するとグローバル変数が変更される。
  myscope = "local";       // 新たなグローバル変数が暗黙的に宣言される。
  return [scope, myscope]; // 2 つの値を返す。
}

checkscope2()              // => ["local", "local"]
scope                      // => "local": グローバル変数が変更された。
myscope                    // => "local": グローバル変数が追加された。

関数はネストして定義できます。ネストした関数は、それぞれの関数ごとに独立したローカルスコープを持ちます。各ローカルスコープはネスト構造になります。

var scope = "global scope";     // グローバル変数。

function checkscope() {
  var scope = "local scope";    // ローカル変数。
  function nested() {
    var scope = "nested scope"; // ネストされたローカル変数。
    return scope;               // このスコープでの値が返される。
  }
  return nested();
}

checkscope()                    // "nested scope"

関数のスコープとホイスティング

プログラミング言語の中には、中括弧で囲まれたブロックごとにスコープを持つものがあります。このようなプログラミング言語では、変数は宣言されたブロックの外からは参照できません。このようなスコープを ブロックスコープ と呼びますが、JavaScript にはこのようなブロックスコープはありません。JavaScript では、その代わりに 関数スコープ を使っています。

変数は、その変数が定義された関数と、その関数にネストされている関数中からアクセスできます。次に示すコードでは、変数 i、j、k が異なる場所で宣言されていますが、すべてスコープは同じです。3 つの変数は、関数の本体のどこからでもアクセスできます。

function test(o) {
  var i = 0;                    // i は関数全体で定義。
  if (typeof o == "object") {
    var j = 0;                  // j はブロック内だけでなく、関数全体で定義。
    for(var k=0; k < 10; k++) { // k はループ内だけでなく、関数全体で定義。
      console.log(k);           // 0 から 9 まで出力する。
    }
    console.log(k);             // k は定義されたままで、10 が出力される。
  }
  console.log(j);
}

JavaScript では関数スコープが使われるので、関数中で宣言された変数はすべて、関数全体からアクセスできます。変数宣言よりも前のコードからもアクセスできます。この特徴的な仕組みは公式な用語ではありませんが、ホイスティング (巻き上げ) と呼ばれています。JavaScript のコードは、関数中の変数宣言を、関数の先頭にホイスティングしたかのように振る舞います。

var scope = "global";

function f() {
  console.log(scope);  // "undefined" が出力される。"global" ではない。
  var scope = "local"; // 変数はここで初期化されるが、定義は関数全体で有効。
  console.log(scope);  // "local" が出力される。
}

上記の例では、関数の最初の行で global が出力されると考えるかもしれません。しかし、ローカル変数は関数全体で有効であり、同じ名前のグローバル変数は参照できなくなることを思い出して下さい。ただし、4 行目ではローカル変数はすでに有効なのですが、var 文が実行されていないので、初期値はまだ設定されていません。そのため、次の例のように変数宣言をホイスティングし、初期化を元の場所で実行するように書き換えたのと同じ意味になります。

function f() {
  var scope;          // 関数の先頭でローカル変数は宣言される。
  console.log(scope); // ここで変数は有効。ただし値は"undefined"。
  scope = "local";    // ここで変数を初期化し、値を設定する。
  console.log(scope); // そして、ここでは設定した値が保持されている。
}

ブロックスコープを持つプログラミング言語では、変数を利用する場所のできるだけ近くで宣言するようにして、スコープをできるだけ狭くすることが、よいプログラミングスタイルだと一般的に言われています。一方で、JavaScript にはブロックスコープがないので、すべての変数を関数の先頭で宣言するようにしている場合もあります。このようにすることで、変数の本当のスコープとソースコードが同じになり、コードがわかりやすくなります。

プロパティとしての変数

JavaScript のグローバル変数を宣言は、グローバルオブジェクトのプロパティを定義することです。var を使って変数を宣言すると、作成されるプロパティは再定義不可 (nonconfigurable) になります。再定義不可は、delete 演算子を使用しても削除できません。非 strict モードでは、宣言していない変数に対して値を代入すると、自動的にグローバル変数を生成します。このような方法で生成された変数は、再定義可能 (configurable) なプロパティとして生成されるので、削除できます。

var truevar = 1;     // 正しく宣言されたグローバル変数。削除不可。
fakevar = 2;         // グローバルオブジェクトの削除可能なプロパティを生成。
this.fakevar2 = 3;   // 同上。

delete truevar       // false: 変数は削除されない。
delete fakevar       // true : 変数は削除された。
delete this.fakevar2 // true : 変数は削除された。

JavaScript のグローバル変数は、グローバルオブジェクトのプロパティになります。このことは、ECMAScript 仕様で標準化されています。ローカル変数の場合には、このような要件はありません。しかし、ローカル変数についても、関数呼び出しに関連付けられたオブジェクトのプロパティとして扱うこともできます。ECMAScript 3 仕様では、このオブジェクトのことを Call オブジェクトと呼んでいます。また、ECMAScript 5 仕様では Declarative Environment Record と呼んでいます。JavaScript では、this キーワードを使ってグローバルオブジェクトを参照できます。しかし、ローカル変数が保存されているオブジェクトを参照する方法はありません。ローカル変数を保持するオブジェクトについては、本来実装上の問題ですので、JavaScript プログラマが気にする必要のないことです。しかし、ローカル変数用のオブジェクトが存在する概念は重要ですので、次の項でもう少し詳しく説明します。

スコープチェーン

スコープチェーンとは、コードに対してスコープ内にある変数を定義するオブジェクトのリスト (チェーン) です。JavaScript が変数 x の値を調べる必要がある場合 (この処理を、変数の名前解決と呼びます)、チェーンの先頭のオブジェクトから検索を始めます。オブジェクトが x のプロパティが存在する場合、そのプロパティの値が使われます。オブジェクトが x のプロパティが存在しない場合、チェーンの次のオブジェクトに対して検索を行います。スコープチェーンのどのオブジェクトにも x のプロパティが存在しない場合、x はこのコードのスコープ内に存在せず、ReferenceError が発生します。

トップレベルコード (関数に含まれていないコード部分) では、スコープチェーンに含まれるオブジェクトはグローバルオブジェクト 1 つだけです。ネストしていない関数の場合、スコープチェーンには 2 つのオブジェクトが含まれます。先頭のオブジェクトには、関数の引数やローカル変数が定義され、2 番目のオブジェクトがグローバルオブジェクトです。ネストしている関数では、スコープチェーンには 3 個以上のオブジェクトが含まれます。このオブジェクトのチェーンは次のように作成されます。

まず、関数が定義されたときに、現在有効なスコープチェーンを保存しておきます。関数が呼び出されたときに、新たにオブジェクトを生成し、ローカル変数を保存します。そして、この新しいオブジェクトを保存しておいたスコープチェーンに追加し、関数呼び出し時のスコープチェーンを表す新たなスコープチェーンを生成します。ネストされた関数の場合はもっと面白いことが起こります。外側の関数が呼び出されるたびに、内側の関数が再び定義されるからです。外側の関数の呼び出しごとに、スコープチェーンが異なりますので、内側の関数を再定義するたびに差異が生じます。内側の関数のコードは同一で、変更されてはいないのですが、コードに関連付けられるスコープチェーンが異なるからです。

式と演算子

JavaScript インタプリタが評価して値を生成できるものを式と呼びます。変数名も単純な式の 1 つです。評価すると、変数に代入された値になります。

単純な式から複雑な式を構成するときに使われるのが演算子です。演算子は、オペランドの値を組み合わせて評価し新しい値を生み出します。オペランドは 2 つの場合が一般的です。例えば、乗算演算子を使った x * y の式は、xy の式の値を評価して新しい値を生成します。

単項式

最も単純な式は、ほかの式を含まない式です。このような式を 単項式 と呼びます。JavaScript での単項式としては、定数値 (リテラル)、JavaScript のキーワード、変数参照があります。リテラルとは、プログラム中に直接埋め込まれた定数値です。

1.23      // 数値リテラル。
"hello"   // 文字列リテラル。
/pattern/ // 正規表現リテラル。

JavaScript の予約語には、単項式として使えるものもあります。

true  // 論理値のtrue の値に評価される。
false // 論理値のfalse の値に評価される。
null  // null 値に評価される。
this  // 「現在の」オブジェクトに評価される。

プログラム中のさまざまな場所で、さまざまな値に評価されます。this キーワードは、オブジェクト指向プログラミングでよく使われるキーワードです。メソッド本体中で this を使って、メソッドが呼び出されたオブジェクトを参照できます。

i         // 変数i の値に評価される。
sum       // 変数sum の値に評価される。
undefined // undefined はグローバル変数。null と異なりキーワードではない。

プログラム中に識別子が現れた場合、JavaScript は変数と認識し、値を検索します。該当する名前の変数が存在しない場合、式は undefined 値に評価されます。ただし、ECMAScript 5 の strict モードでは、存在しない変数を評価しようとすると ReferenceError 例外がスローされます。

オブジェクトと配列の初期化子

オブジェクトと配列の初期化子とは、新たに生成されるオブジェクトや配列の値となる式のことです。初期化子は、オブジェクトリテラル配列リテラルと呼ばれますが、本当のリテラルとは異なり、単項式ではありません。なぜなら、プロパティ値や、要素の値を指定するために、複数の式が含まれるからです。

配列初期化子は、カンマで区切ったリストを角括弧で囲んだものです。配列初期化子の値は、新たに生成された配列になります。この配列の要素は、カンマで区切られた式の値に初期化されます。

[]        // 空の配列。角括弧中に式がない場合、要素がないことを意味する。
[1+2,3+4] // 要素を 2 つ持つ配列。最初の要素が 3 に、2 つ目の要素が 7 になる。

配列初期化子中の要素の式として、配列初期化子を記述できます。この場合、ネスト配列が生成されます。

var matrix = [[1,2,3], [4,5,6], [7,8,9]];

配列初期化子が評価されるたびに、配列初期化子中の要素の式も評価されます。つまり、配列初期化子の値は、評価されるたびに異なる可能性があります。未定義の要素を配列リテラル中に含めたい場合は、カンマの間の値を省略してください。例えば、以下の例では、要素を 5 つ持ちますが、うち 3 つの要素は未定義の要素になります。

var sparseArray = [1,,,,5];

配列初期化子の最後の式の後にカンマを 1 つ記述しても問題ありません。この場合は、未定義要素は作成されません。オブジェクト初期化子は、配列初期化子と同じような書式ですが、角括弧の代わりに中括弧を使い、各式の前にはプロパティ名とコロンを記述します。

var p = { x:2.3, y:-1.2 }; // 2 つのプロパティを持つオブジェクト。
var q = {};                // プロパティを持たない空のオブジェクト。
q.x = 2.3; q.y = -1.2;     // これで、q はp と同じプロパティを持つ。

オブジェクトリテラルは、次の例のように入れ子にできます。

var rectangle = { upperLeft:  { x: 2, y: 2 },
                  lowerRight: { x: 4, y: 5 } };

オブジェクト初期化子が評価されるたびに、オブジェクト初期化子中の式も評価されます。この式には、定数だけでなく、任意の式が記述できます。また、オブジェクトリテラルのプロパティ名は識別子ではなく文字列です。プロパティ名として予約語が使えたり、識別子として適切ではない文字列も使えます。

var side = 1;
var square = { "upperLeft":  { x: p.x, y: p.y },
               'lowerRight': { x: p.x + side, y: p.y + side }};

関数定義式

関数定義式は、JavaScript の関数を定義します。この式の値は、新たに定義される関数になります。オブジェクト初期化子が "オブジェクトリテラル" と呼ばれるのと同じように、関数定義式も関数リテラル と呼びます。関数定義式は、一般的には、function キーワードと 0 個以上の識別子 (仮引数名) を持ちます。

// この関数は、引数として渡された値の自乗を返します。
var square = function(x) { return x * x; }

関数定義式には 関数名 を記述できます。関数は関数定義式の代わりに関数文を使って定義もできます。

プロパティアクセス式

プロパティアクセス式が評価されると、オブジェクトプロパティの値、または、配列の要素の値になります。JavaScript には、プロパティアクセス用に次の 2 つの構文が用意されています。

式.識別子
式[ 式 ]

前者の方法では、式の後にピリオドと識別子を記述します。式がオブジェクトを指定し、識別子がアクセスしたいプロパティの名前を指定します。

後者の方法では、最初の式 (オブジェクト、または配列) の後に、角括弧で囲んだ式が続きます。この方法では、アクセスしたいプロパティの名前や、アクセスしたい配列要素のインデックスを指定します。

var o = {x:1,y:{z:3}}; // 例となるオブジェクト。
var a = [o,4,[5,6]];   // オブジェクトを含む配列の例。

o.x                    // 1: 式 o のプロパティ x の値。
o.y.z                  // 3: 式 o.y のプロパティ z の値。
o["x"]                 // 1: オブジェクト o のプロパティ x の値。
a[1]                   // 4: 式 a のインデックス 1 の要素の値。
a[2]["1"]              // 6: 式 a[2] のインデックス 1 の要素の値。
a[0].x                 // 1: 式 a[0] のプロパティ x の値。

どちらのプロパティアクセス式も、"." や "[" の前にある式が先に評価されます。評価した値が nullundefined になった場合、TypeError 例外がスローされます。この 2 つの値は、プロパティを持てないからです。

値がオブジェクトでも配列でもない場合は、型変換が行われます。オブジェクト式の後に、"." と識別子が続く場合、識別子で指定されたプロパティ名の値が検索され、この値がプロパティアクセス式全体の値となります。オブジェクト式の後に角括弧が続く場合は、2 番目の式を評価し文字列に変換します。この文字列と同じ名前のプロパティの値が、プロパティアクセス式全体の値になります。どちらの場合も、指定された名前のプロパティが存在しない場合は、プロパティアクセス式の値は undefined になります。

呼び出し式

呼び出し式は、JavaScript において、関数やメソッドを呼び出す(実行する)ための構文です。まず、呼び出される関数を指定するための関数式を記述します。この関数式の後に、開き丸括弧を記述し、カンマで区切って 0 個以上の引数式を記述し、最後に閉じ丸括弧を記述します。

f(0)            // f は関数式、0 は引数式。
Math.max(x,y,z) // Math.max は関数、x、y、z は引数。
a.sort()        // a.sort は関数、引数はない。

呼び出し式が評価されるときは、まず関数式が評価され、続いて引数式が評価され、引数の値のリストが生成されます。関数式の値が、呼び出し可能なオブジェクトにならない場合は、TypeError 例外がスローされます。関数は、return 文を使うと値を返すことができます。この値が、呼び出し式の値となります。return 文がない場合には、呼び出し式の値は undefined になります。

呼び出し式は、一対の丸括弧と、開き丸括弧の前の式から構成されます。この式がプロパティアクセス式の場合には、メソッド呼び出しになります。メソッド呼び出しの場合は、プロパティアクセスの対象となるオブジェクトや配列が this 引数の値となります。そして、この this の値を関数本体で利用できるようになります。

メソッド呼び出しではない呼び出し式の場合は、this キーワードの値として使われるのは一般的にはグローバルオブジェクトです。しかし、ECMAScript 5 では、strict モードで定義された関数については、this の値はグローバルオブジェクトではなく undefined になります。

オブジェクト生成式

オブジェクト生成式は、新たにオブジェクトを生成し、コンストラクタと呼ばれる関数を呼び出して、オブジェクトのプロパティを初期化します。オブジェクト生成式は、呼び出し式と非常に似ていますが、式の前に new キーワードを記述する点が異なります。

new Object()
new Point(2,3)

オブジェクト生成式中のコンストラクタ関数に引数を渡さない場合は、丸括弧を省略してもかまいません。

new Object
new Date

オブジェクト生成式が評価されると、まず空のオブジェクトを生成します。空のオブジェクトは、オブジェクト初期化子 {} で生成したオブジェクトと同じものです。次に、指定した引数を、指定した関数に渡して呼び出します。このとき、this キーワードの値として、新たに生成したオブジェクトを設定して関数を呼び出します。この関数の中では、this を使って、新たに生成したオブジェクトのプロパティを初期化できます。コンストラクタ用の関数は、値を返してはいけません。オブジェクト生成式の値は、新たに作成され、初期化されたオブジェクトになります。コンストラクタがオブジェクト値を返した場合、このオブジェクト値がオブジェクト生成式の値になり、新たに生成されたオブジェクトは捨てられてしまいます。

演算子の概要

JavaScript の演算子は、算術式、比較式、論理式、代入式などで使われます。ほとんどの演算子は += などの記号で表されますが、deleteinstanceof のようにキーワードで表される演算子もあります。キーワードで表される演算子も読みやすい表記になっているだけで、記号で表される演算子と同じく普通の演算子です。

JavaScript の演算子は、下表に演算子の優先順位順にまとめてあります。

  • A (associativity) 列は、式の結合性を示します。L は左から右の順 (left-to-right) で評価します。R は右から左の順 (right-to-left) で評価します。
  • N 列は、オペランドの数を表します。
  • 型 列は、想定するオペランドの型と、→ の後に、演算子の結果の型を列挙しています。
JavaScript の演算子
演算子説明AN
++
--
-
+
~
!
delete
typeof
void
前置または後置のインクリメント
前置または後置のデクリメント
数値の符号反転
数値に変換
ビット単位補数
論理補数
プロパティの削除
オペランドのデータ型を返す
未定義値を返す
R
R
R
R
R
R
R
R
R
1
1
1
1
1
1
1
1
1
左辺値→数値
左辺値→数値
数値→数値
数値→数値
整数→整数
論理値→論理値
左辺値→論理値
任意→文字列
任意→未定義値
*, /, % 乗算、除算、剰余 L 2 数値、数値→数値
+, -
+
加算、減算
文字列の連結
L
L
2
2
数値、数値→数値
文字列、文字列→文字列
<<
>>
>>>
左シフト
右シフト
右シフト
L
L
L
2
2
2
整数、整数→整数
整数、整数→整数
整数、整数→整数
<, <=, >, >=
<, <=, >, >=
instanceof
in
数値順で比較
アルファベット順で比較
オブジェクトのクラスを調べる
プロパティが存在するかを調べる
L
L
L
L
2
2
2
2
数値、数値→論理値
文字列、文字列→論理値
オブジェクト、関数→論理値
文字列、オブジェクト→論理値
==
!=
===
!==
値が等しいかどうかをテストする
値が等しくないかどうかをテストする
値が同じであるかどうかをテストする
値が同じでないかどうかをテストする
L
L
L
L
2
2
2
2
任意、任意→論理値
任意、任意→論理値
任意、任意→論理値
任意、任意→論理値
& ビット単位 AND L 2 整数、整数→整数
^ ビット単位 XOR L 2 整数、整数→整数
| ビット単位 OR L 2 整数、整数→整数
&& 論理 AND L 2 任意、任意→任意
|| 論理 OR L 2 任意、任意→任意
?: 条件演算子 L 3 論理値、任意、任意→任意
=
*=, /=, %=, +=, -=, &=, ^=, |=, <<=, >>=, >>>=
代入
演算を伴う代入
R
R
2
2
左辺値、任意→任意
左辺値、任意→任意
, 最初のオペランドを無視し、2 番目のオペランドを返す L2任意、任意→任意

オペランドの数

演算子はオペランドの数で 単項演算子二項演算子三項演算子 の 3 種類に大別できます。

単項演算子は 1 つの式を複雑な別の式に変換します。例えば、-x の式の "-" がそうです。この演算子は、オペランド x に対して符号反転を実行します。

二項演算子は乗算演算子 (*) などがそれにあたります。2 つの式を組み合わせて、より複雑な 1 つの式を作ります。操作対象となるオペランドが 2 つ必要なので、二項演算子と呼ばれます。

三項演算子は、条件演算子 (?:) だけです。?: は、3 つの式を組み合わせて 1 つの式を作る演算子です。

オペランドと演算結果の型

多くの演算子はオペランドとして、ある特定の型を必要とし、ある特定の型の値を返します。上表の "型" 列にオペランドの型と演算子の演算結果の型を示しています。矢印元がオペランドの型で、矢印先が演算結果の型です。

JavaScript の演算子は、必要に応じてオペランドの型を変換します。例えば、乗算演算子 (*) は、オペランドとして数値を必要とします。しかし、"3" * "5" の式は、オペランドを数値に変換するため、正常に処理されます。この式の値は 15 になります (文字列の "15" ではありません)。また、値は true と評価されるものと false と評価されるもののどちらかになります。したがって、オペランドとして論理値が必要な演算子でも、任意の型のオペランドに対して問題なく動作します。

オペランドの型によって働きが変わる演算子もいくつかあります。例えば、+ 演算子の場合、オペランドの型が数値なら加算を実行しますが、オペランドの型が文字列なら連結処理を行います。同じように、< などの比較演算子は、オペランドの型によって、数値の大小で比較したり、アルファベット順で比較したりします。

左辺値

代入演算子などいくつかの演算子で、オペランドの型として 左辺値 を必要とするものがあります。左辺値とは、代入演算子の左側に記述しても問題のない式です。JavaScript の場合は、変数やオブジェクトのプロパティ、配列の要素が左辺値になります。ECMAScript 仕様では、組み込み関数が左辺値を返してもよいことになっています。

演算子の副作用

2 * 3 のような単純な式を評価したとしても、プログラムの状態は何も変化しません。また、今後行う計算にも、この式の評価は何も影響しません。しかし、式の中には 副作用 を持つものがあります。

変数やプロパティに値を代入すれば、今後その値やプロパティを使う式の値が変化します。同じように、インクリメント演算子 (++) やデクリメント演算子 (--) も暗黙的に代入を行うので副作用が生じます。delete 演算子も副作用があります。プロパティを削除することは、プロパティに undefined を代入するのとほぼ同じ意味になるからです。

JavaScript では、このほかの演算子に副作用はありません。ただし、関数呼び出し式やオブジェクト生成式は、関数中で前述したような演算子が使われた場合に、副作用を持つことになります。

演算子の優先順位

上表に列挙した演算子は、優先順位の高いものから順に並べています。同じ優先順位のものは、グループに分けています。演算子が複数ある場合に、どの演算子から演算を行うかは、この優先順位で制御されます。つまり、表の上のほうに挙げている優先順位の高い演算子から処理が行われます。

w = x + y * z;

乗算演算子 * のほうが加算演算子 + より優先順位が高いので、まず乗算が行われ、次に加算が行われます。代入演算子 = は優先順位が最も低いので、右側の処理がすべて終了した後に実行されます。優先順位を変更したい場合は、括弧を使用します。上記の例で、加算を実行してから乗算を実行したい場合は次のようにします。

w = (x + y) * z;

プロパティアクセス式と呼び出し式は、すべての演算子よりも優先順位が高くなります。

typeof my.functions[x](y)

typeof は優先順位が最高の演算子ですが、プロパティアクセスや関数呼び出しがまず実行され、その結果に対して typeof 演算が行われます。演算子の優先順位がよくわからない場合は、括弧を使用して明示的に評価順序を指定することを推奨します。

演算子の結合性

上表で、A (associativity) は演算子の結合性を示しています。A 列の L (left-to-right) は左から右に演算子を結合します。同様に R (right-to-left) は右から左へ演算子を結合します。このようなルールは、優先順位が同じ演算子の実行順序を決めるときに必要になります。L が指定されていれば、左から右へ順に処理されます。

w = x - y - z;

減算演算子は左から右に処理されるので、この式は次のように括弧を指定したのと同じです。

w = ((x - y) - z);

一方、次の例を見てください。


x = ~-y;
w = x = y = z;
q = a?b:c?d:e?f:g;

単項演算子や代入演算子、条件演算子は右から左へ処理されるので、これらの式は次のように記述したのと同じです。

x = ~(-y);
w = (x = (y = z));
q =a?b:(c?d:(e?f:g));

評価順序

演算子の優先順位と結合性により、複雑な式の中での演算子の処理順序が決められます。しかし、優先順位と結合性は、個々の式が評価される順序までは指定しません。JavaScript では、常に式の評価は左から右に行われます。例えば、w = x + y * z の式の場合、まず w が評価され、その後、x、y、z の順序で評価が行われます。そして、y と z の値が乗算され、x の値と加算され、w の式で指定された変数やプロパティに代入が行われます。式に対して、括弧を追加すれば、乗算や加算、代入の順序を変更できますが、評価の順序 (左から右) は変更できません。

評価の順序が問題となるのは、評価される式の中に副作用を持ち、この副作用がほかの式に影響を及ぼす場合だけです。もしも先ほどの例で、z 式で使う変数が x 式の中でインクリメントされる場合、x が z よりも前に評価されるため、評価順序には注意して下さい。

算術演算子

算術演算子には、加算演算子減算演算子乗算演算子除算演算子剰余演算子 があります。また、その他にも単項演算子、ビット演算子などもあります。加算演算子や、単項演算子、ビット演算子については後述します。

減算演算子、乗算演算子、除算演算子、剰余演算子については、オペランドを評価し、必要に応じて値を数値に変換し、差、積、商、余りを計算するだけです。オペランドが数値に変換できない場合には、NaN 値に変換します。オペランドの一方が NaN に変換される値の場合、演算結果も NaN になります。除算演算子と剰余演算子は注意が必要ですので、詳しく述べます。

整数と浮動小数点数を区別するようなプログラミング言語に慣れている場合、整数を整数で割ると、商も整数になると考えるかもしれません。しかし、JavaScript の場合は、すべての数値は浮動小数点数なので、除算の結果も浮動小数点数になります。つまり、5/2 の結果は 2 ではなく、2.5 になります。0 で除算した場合は、正または負の無限大になります。0/0 はNaN になります。いずれの場合もエラーにはならないため、注意が必要です。

余剰演算子は、最初のオペランドを 2 番目のオペランドで割った余りを計算します。つまり、最初のオペランドを 2 番目のオペランドで整数割り算をしたときの余りを返します。演算結果の符号は、最初のオペランドの符号と同じになります。例えば、5 % 2 は 1 になり、-5 % 2 は -1 になります。なお、剰余演算子は整数値に対して使われるのが一般的ですが、浮動小数点数に対しても問題なく動作します。例えば、6.5 % 2.1 は 0.2 になります。

加算演算子

二項演算子の加算演算子は、数値オペランドであれば加算を行い、文字列オペランドの場合は連結を行います。

1 + 2                   // => 3
"hello" + " " + "there" // => "hello there"
"1" + "2"               // => "12"

加算演算子の型変換規則では、文字列の連結が優先的に行われます。オペランドの一方が文字列、または文字列に変換できるオブジェクトの場合、もう一方のオペランドは文字列に変換され、連結処理が行われます。両方のオペランドとも文字列ではない場合にのみ、加算演算が行われます。正確に言えば、加算演算子は次のように振る舞います。

  • オペランドの値のどちらかがオブジェクトの場合、オブジェクトから基本型値に変換します。つまり、Date オブジェクトは、toString() メソッドを使って変換を行います。そのほかのオブジェクトは、valueOf() メソッドが基本型値を返すのであれば、valueOf() メソッドを使って型変換を行います。しかし、多くのオブジェクトは意味のある valueOf() メソッドを持たないので、toString() を使って型変換を行います。
  • オブジェクトから基本型への型変換の後、オペランドの一方が文字列の場合、もう一方のオペランドも文字列に変換し連結処理を行います。
  • それ以外の場合は、両方のオペランドを数値 (または NaN) に変換し、加算処理が行われます。
1 + 2         // 3: 加算。
"1" + "2"     // "12": 連結。
"1" + 2       // "12": 数値から文字列への変換の後、連結。
1 + {}        // "1[object Object]": オブジェクトから文字列への変換の後、連結。
true + true   // 2: 論理値から数値への変換の後、加算。
2 + null      // 2: null を 0 に変換した後、加算。
2 + undefined // NaN: undefined を NaN に変換した後、加算。

文字列と数値に対して加算演算子を使った場合は、演算子の実行順序によって演算結果が変わってしまうため、結合法則が成り立たない場合があります。

1 + 2 + " blind mice";   // "3 blind mice"
1 + (2 + " blind mice"); // "12 blind mice"

1 行目では、括弧がなく、加算演算子は左から右に処理されるので、2 つの数値がまず加算されます。そして、その加算結果が文字列に連結されます。2 行目では、括弧によって演算順序が変更されます。数値の 2 が、連結されて新しい文字列が生成されます。次に、数値 1 も文字列として連結され、最終的な文字列が得られます。

単項算術演算子

単項演算子は、1 つのオペランドの値に対して処理を行い、新たな値を生成します。JavaScript では、単項演算子はすべて優先順位が高く、すべて右結合性を持ちます。単項算術演算子 (+、-、++、--) はすべて、必要に応じてオペランドを数値に変換します。なお、+- は、単項演算子としても二項演算子としても使われる点に注意してください。単項算術演算子を以下に示します。

  • 単項プラス (+) 単項プラス演算子は、オペランドを数値 (または NaN) に変換して返します。オペランドがすでに数値の場合には、この演算子は何もしません。
  • 単項マイナス (-) 単行マイナス演算子は、オペランドを数値に変換し、変換結果の符号を反転して返します。
  • インクリメント (++) インクリメント演算子は、オペランドをインクリメントします。オペランドは、変数、配列の要素、オブジェクトのプロパティなどの、左辺値でなければなりません。オペランドを数値に変換し、この数値に 1 を加算し、加算した結果を変数、要素、プロパティに代入し直します。インクリメント演算子の戻り値は、オペランドとの位置関係で変わります。前置インクリメント演算子の場合、オペランドをインクリメントし、インクリメント後の値が評価されます。後置インクリメント演算子の場合、オペランドをインクリメントし、インクリメント前の値が評価されます。++x は、x=x+1 と常に同じわけではありません。インクリメント演算子は、文字列の連結処理を行いません。インクリメント演算子は、オペランドを常に数値に変換し、インクリメントします。x が文字列の "1" の場合、++x は数値の 2 になります。これに対して、x+1 は文字列の "11" になります。また、JavaScript が自動的にセミコロンを挿入するために、オペランドと後置インクリメント演算子の間には改行を入れられない点にも注意してください。もしも改行を入れてしまうと、JavaScript はオペランドだけで 1 つの完成した文と判断し、後置演算子の前にセミコロンを挿入してしまいます。このインクリメント演算子は、前置形式のものも後置形式のものも、for ループを制御するカウンタをインクリメントするときによく使われます。
  • デクリメント (--) デクリメント演算子は、オペランドとして左辺値を必要とします。オペランドを数値に変換し、この数値から 1 を減算し、加算した結果をオペランドに代入し直します。デクリメント演算子の戻り値は、オペランドとの位置関係で変わります。前置デクリメント演算子の場合、オペランドをデクリメントし、デクリメント後の値が評価されます。後置デクリメント演算子の場合、オペランドをデクリメントし、デクリメント前の値が評価されます。

ビット演算子

ビット演算子は、2 進数における数値のビット操作をするための低レベルの演算子です。厳密にはビット演算子は一般的な算術演算は行わないため、算術演算子に分類しませんが、演算結果も数値であるためここで説明します。

ビット演算子は、オペランドとして整数値が必要です。また、この整数値は、64 ビットの浮動小数点表現形式ではなく、32 ビット整数表現形式で表されているものとして処理を行います。これらの演算子は、必要に応じてオペランドを数値に変換し、オペランドの小数点部分を削除したり、33 ビット目以上のビットを捨てたりすると、32 ビットの整数表現で表せるようにします。

シフト演算子の右側オペランドには、0 から 31 までの数値が必要です。まず、オペランドを符号なしの 32 ビットの整数に変換した後に、2 進数で表記した場合に 6 ビット目以上のビットを無視すると、0 から 31 までに収まるようにします。また、NaNInfinity-Infinity をビット演算子のオペランドとして使うと 0 に変換されます。

  • ビット積演算子 (&) ビット積演算子は、整数引数の各ビット単位で論理積を計算します。両方のオペランドでそれぞれ対応するビットが 1 のときに、結果のビットが 1 になります。例えば、0x1234 & 0xFF の評価結果は 0x0034 になります。
0x 0001 0010 0011 0100 // 0x1234
0x 0000 0000 1111 1111 // 0xFF
-------------------------
0x 0000 0000 0011 0100 // 0x0034
  • ビット和演算子 (|) ビット和演算子は、整数引数の各ビット単位で論理和を計算します。どちらか一方または両方のオペランドでそれぞれ対応するビットが 1 のときに、結果のビットが 1 になります。例えば、0x1234 | 0x00FF の評価結果は 0x12FF になります。
0x 0001 0010 0011 0100 // 0x1234
0x 0000 0000 1111 1111 // 0xFF
-------------------------
0x 0001 0010 1111 1111 // 0x12FF
  • ビット排他的論理和演算子 (^) ビット排他的論理和演算子は、整数引数の各ビット単位で排他論理和を計算します。2 つのオペランドのどちらか一方で対応するビットが 1 のときに(両方が 1 の場合を除く)、結果のビットが 1 になります。排他論理和はどちらか一方が true で、両方は true ではありません。例えば、0xFF00 ^ 0xF0F0 の評価結果は 0x0FF0 になります。
0x 1111 1111 0000 0000 // 0xFF00
0x 1111 0000 1111 0000 // 0xF0F0
-------------------------
0x 0000 1111 1111 0000 // 0x0FF0
  • ビット否定演算子 (~) ビット否定演算子は単項演算子です。オペランドは整数引数が 1 つだけです。ビット否定演算子は、オペランドのすべてのビットを反転させます。JavaScript では、符号を変えて 1 を引いた値になります。例えば、~0x0F (15) の評価結果は 0xFFFFFFF0 (-16) になります。
0x 0000 0000 0000 0000 0000 0000 0000 1111 // 0x00000001
---------------------------------------------
0x 1111 1111 1111 1111 1111 1111 1111 0000 // 0xFFFFFFF0
  • 左シフト演算子 (<<) 左シフト演算子は、前のオペランドのすべてのビットを、後ろのオペランドに指定された数だけ左側に移動させます。この数は 0 から 31 までの範囲の整数でなければなりません。例えば、a << 1 は、a の右端のビットを右から 2 番目のビットに、a の右から 2 番目にあったビットを 3 番目に...、順にシフトしていきます。新しい右端のビットにはゼロが入り、元の 32 番目のビットは捨てられます。このように 1 つずつビット位置をシフトする操作は、元の値に 2 を掛ける演算と同じになります。ビット位置を 2 つシフトすると、4 倍したことになります。例えば、7 << 2 の評価結果は 28 になります。
0x 0000 0000 0000 0111 // 0x0007
-------------------------
0x 0000 0000 0001 1100 // 0x0028
  • 符号付き右シフト演算子 (>>) 符号付き右シフト演算子は、前のオペランドのすべてのビットを、後ろのオペランドに指定された数だけ右側に移動させます。この数は 0 から 31 までの範囲の整数でなければなりません。右側からはみ出たビットは捨てられます。左端には、元のオペランドの符号ビットと同じ値が入ります。これは、結果の符号を元と同じ符号にするためです。元のオペランドが正なら、左端のビットにゼロが入ります。元のオペランドが負なら、左端ビットに1 が入ります。ビット位置を右に 1 つだけシフトすると、2 で割って余りを捨てるのと同じ結果になります。ビット位置を右に 2 つシフトすると、4 で割って余りを捨てるのと同じ結果になります。例えば、7 >> 1-7 >> 1 の評価結果は、それぞれ 3 と -4 になります。
0x 0000 0000 0000 0111 // 0x0007
-------------------------
0x 0000 0000 0000 0011 // 0x0003
  • 符号なし右シフト演算子 (>>>) 符号なし右シフト演算子は、左端に入る値が前のオペランドの符号にかかわらず常に 0 であること以外は、符号付き右シフト演算子と同じです。例えば、-1 >> 4-1 >>> 4 の評価結果は、それぞれ -10x0FFFFFFF になります。
0x 1111 1111 1111 1111 1111 1111 1111 1110 // 0xFFFFFFFE (-1)
------------------------------------------
0x 0000 1111 1111 1111 1111 1111 1111 1111 // 0x0FFFFFFF

関係演算子

関係演算子は、2 つの値の関係 (例えば、"等しい" や "より小さい" や "プロパティかどうか" など) を調べて、その結果を true または false で返します。この論理値は、if 文、while 文、for 文でプログラムの実行を制御するときによく使われます。

等値演算子と不等演算子

等値演算子 (==) と 同値演算子 (===) は、2 つのオペランドの値が同じかどうかを調べます。オペランドが等しい場合は true を返し、異なる場合は false を返します。ただし、等値演算子と同値演算子では "同じ" の定義が異なります。=== 同値演算子は、厳密に比較して2 つのオペランドが "同一" であるかどうかを調べます。== 等値演算子は、もっと緩やかに型変換を行いながら値を比較して、2 つのオペランドが "等しい" かどうかを調べます。

JavaScript には、====== 演算子があります。代入演算子、等値演算子、同一演算子の違いをよく理解して、適切な演算子を使うようにしてください。

不等演算子 (!=) と非同値演算子 (!==) は、それぞれ等値演算子 (==) と同値演算子 (===) の逆になります。不等演算子 (!=) は、== 演算子で 2 つの値が等しい場合に false を返し、等しくない場合に true を返します。非同値演算子 (!==) は、2 つの値が同一の場合に false を返し、同一でない場合に true を返します。

あるオブジェクトは、そのオブジェクト自身とは等しくなりますが、ほかのオブジェクトとは等しくなりません。2 つの異なるオブジェクトが同じ名前と値のプロパティを同じ数だけ持っていたとしても、この2 つのオブジェクトは等しいとは判定されません。同じ要素を同じ順序で持つ 2 つの配列も等しいと判定されません。同値演算子 (===) は、オペランドを評価し、次のような規則で型変換を行わずに 2 つの値を比較します。

  • 2 つの値の型が異なる場合は、2 つの値は等しくないと判定します。
  • 2 つの値が両方とも null、または undefined の場合、等しいと判定します。
  • 2 つの値が論理値の true、または false の場合、等しいと判定します。
  • どちらか一方、または両方とも NaN の場合、等しくないと判定します。NaN 値は、NaN 値自身も含めて、どの値とも等しくなりません。xNaN かどうかを調べるときには、x !== x の式を使ってください。この式が true になるのは、x の値が NaN のときだけです。
  • 両方の値が数値で、同じ値を持つ場合は、等しいと判定します。一方の値が 0 で、もう一方の値が -0 の場合も、等しいと判定します。
  • 両方の値が文字列で、同じ位置に同じ 16 ビット値が含まれる場合、2 つの値を等しいと判定します。文字列の長さや内容が異なる場合は、等しくないと判定します。同じ内容で、同じ見た目になるような 2 つの文字列が、異なる 16 ビット値でエンコードされている場合、JavaScript は Unicode の正規化処理を行わないので、== 演算子や === 演算子では、この 2 つの文字列を等しくないと判定します。このほかに、文字列を比較するための方法として、String.localeCompare() もあります。
  • 両方の値が同じオブジェクトや配列、関数を参照していれば、等しいと判定します。両方の値が異なるオブジェクトを参照している場合は、等しいとは判定されません。両方のオブジェクトが、たとえ同じプロパティを持つ場合でも等しいとは判定されません。

等値演算子は、同値演算子と同じような処理を行いますが、少し緩やかに比較を行います。2 つのオペランドの型が異なる場合は、型変換を行った後に、再度比較を行います。

  • どちらも同じ型の場合は、両方の値が同一かどうかを調べます。値が同一であれば、等しいと判定します。値が同一でなければ、等しくないと判定します。
  • 型が異なる場合は、同じ型に変換してから、以下の順で等しいかどうかを判定します。
    • どちらか一方の値が null で他方が undefined の場合、等しいと判定します。
    • どちらか一方の値が数値で他方が文字列の場合、文字列を数値に変換してから比較を行います。
    • どちらか一方の値が true の場合、true を 1 に変換してから比較を行います。どちらか一方の値が false の場合、false を 0 に変換してから比較を行います。
    • どちらか一方の値がオブジェクトで他方が数値または文字列の場合、オブジェクトを基本型に変換してから比較を行います。オブジェクトを基本型に変換するには、toString() メソッドまたは valueOf() メソッドが使われます。コア JavaScript の組み込みクラスでは、valueOf() 変換が toString() 変換より優先されます。ただし、Date クラスだけは例外で、常に toString() 変換が行われます。
    • 上記以外の場合は、等しくないと判定します。

型変換を伴う等値テストの例を以下に紹介します。

"1" == true

2 つの値は見た目がまったく異なっていますが、この式は true になります。論理値の true がまず 1 に変換され、文字列の "1" が数値の 1 に変換された後、比較が行われます。2 つの値は同じ数値に変換されたので、この比較は等しい (true) 結果になります。

比較演算子

比較演算子は、2 つのオペランドの相対的な順序を調べます。比較演算子には以下のようなものがあります。

  • 小なり演算子 (<) 小なり演算子は、1 番目のオペランドが 2 番目のオペランドより小さいときに true を返します。それ以外のときには false を返します。
  • 大なり演算子 (>) 大なり演算子は、1 番目のオペランドが 2 番目のオペランドより大きいときに true を返します。それ以外のときには false を返します。
  • 小なりイコール演算子 (<=) 小なりイコール演算子は、1 番目のオペランドが 2 番目のオペランドに等しいか、あるいはそれより小さいときに true を返します。それ以外のときには false を返します。
  • 大なりイコール演算子 (>=) 大なりイコール演算子は、1 番目のオペランドが 2 番目のオペランドに等しいか、あるいはそれより大きいときに true を返します。それ以外のときには false を返します。

比較演算子のオペランドには任意の型が指定できます。しかし、比較は数値と文字列のどちらかの形式で行われるので、数値でも文字列でもないものは、そのどちらかに変換されます。型変換と比較は次の規則に従います。

  • どちらかのオペランドがオブジェクトの場合、valueOf() メソッドが基本型値を返す場合は、この値が使われます。基本型値を返さない場合は、toString() メソッドの戻り値が使われます。
  • オブジェクトから基本型への変換の後、両方のオペランドが文字列の場合、この 2 つの文字列をアルファベット順で比較します。ここでのアルファベット順とは、文字列を構成する 16 ビット Unicode 値の数値順序になります。
  • オブジェクトから基本型への変換の後、少なくとも片一方のオペランドが文字列ではない場合、両方のオペランドが数値に変換され、数値的に比較されます。0 と -0 は等しいと判定されます。Infinity は、ほかの数値よりも大きいと判定されます。-Infinity は、ほかの数値よりも小さいと判定されます。どちらかのオペランドが NaN の場合、または NaN に変換される場合、比較演算子は常に false を返します。

JavaScript では、文字列は 16 ビットの整数値の並びなので、文字列比較は単純に 2 つの文字列の値を数値的に比較するだけです。Unicode で定義されている数値順序は、ある特定の言語やロケールで使われる伝統的な文字順序と一致してない場合もあります。また、文字列は大文字と小文字を区別して比較されます。Unicode では (少なくとも ASCII の範囲では) 大文字は小文字よりも "小さい" と判定されるので注意してください。例えば、"Zoo" は "aardvark" よりも小さいことになります。

もっとしっかりとした文字列比較を行いたい場合は、String.localeCompare() メソッドを使用してください。このメソッドは、ロケール固有のアルファベット順序を考慮に入れて比較を行います。大文字と小文字を区別しないで比較したい場合は、まず文字列をすべて String.toLowerCase() メソッドを使って小文字に変換するか、String.toUpperCase() メソッドを使って大文字に変換した後、比較してください。

+ 演算子も比較演算子も、オペランドが数値か文字列かで振る舞いが変わります。+ 演算子は、文字列処理を優先します。オペランドのいずれかが文字列の場合、連結処理が行われます。比較演算子は、数値を優先します。文字列比較を行うのは、両方のオペランドが文字列の場合のみだけです。

1     +  2  // 加算。結果は 3。
"1"   + "2" // 連結。結果は "12"。
"1"   +  2  // 連結。2 は "2" に変換される。結果は "12"。
11    <  3  // 数値で比較。結果は false。
"11"  < "3" // 文字列で比較。結果は true。
"11"  <  3  // 数値で比較。"11" は 11 に変換される。結果は false。
"one" <  3  // 数値で比較。"one" は NaN に変換される。結果は false。
in 演算子

in 演算子は、左側の値が右側のオブジェクトのプロパティ名であれば true を返します。in 演算子の左側のオペランドは文字列か文字列に変換されるもの、右側のオペランドはオブジェクトでなければなりません。

var point = { x:1, y:1 }; // オブジェクトの定義。
"x" in point;             // true: オブジェクトは"x" プロパティは存在する。
"z" in point              // false: オブジェクトに"z" プロパティは存在しない。
"toString" in point       // true: オブジェクトはtoString メソッドを継承する。

var data = [7,8,9];       // 要素0、1、2 を持つ配列。
"0" in data               // true: 配列には要素"0" が存在する。
1 in data                 // true: 数値は文字列に変換される。
3 in data                 // false: 要素3 は存在しない。
instanceof 演算子

instanceof 演算子は、左側のオブジェクトが右側のクラスのインスタンスであれば true を返します。instanceof 演算子の左側のオペランドはオブジェクト、右側のオペランドはクラスを指定するものでなければなりません。JavaScript では、オブジェクトのクラスはオブジェクトを初期化するコンストラクタ関数で表されます。したがって、instanceof 演算子の右側のオペランドは、普通は関数になります。

var d = new Date();  // Date() コンストラクタを使って新しいオブジェクトを生成する。
d instanceof Date;   // true になる。d はDate() を使って生成された。
d instanceof Object; // true になる。すべてのオブジェクトはObject のインスタンス。
d instanceof Number; // false になる。d はNumber オブジェクトではない。

var a = [1, 2, 3];   // 配列リテラル構文を使って配列を生成する。
a instanceof Array;  // true になる。a は配列だから。
a instanceof Object; // true になる。すべての配列はオブジェクト。
a instanceof RegExp; // false になる。配列は正規表現ではない。

すべてのオブジェクトは Object のインスタンスになります。instanceof で、あるオブジェクトがあるクラスのインスタンスかどうかを判定するときには、スーパークラスについても確認します。instanceof の左側のオペランドがオブジェクトでない場合は、false を返します。右側のオペランドが関数でない場合は、TypeError 例外がスローされます。

instanceof 演算子の動作を理解するためには、JavaScript の継承機構であるプロトタイプチェーンについて理解しておく必要があります。o instanceof f の式を評価するとき、JavaScript は f.prototype を評価し、o のプロトタイプチェーンの中にこの値が存在するかどうかを検索します。存在する場合には、of (または f のスーパークラス) のインスタンスとなり、演算子は true を返します。f.prototypeo のプロトタイプチェーンに含まれない場合は、of のインスタンスではないので、instanceoffalse を返します。

論理演算子

論理演算子の 論理積演算子 (&&)、論理和演算子 (||)、論理否定演算子 (!) は、論理演算を行います。論理演算は、2 つの関係演算式を組み合わせて式を作成するのに使われます。

論理積演算子

&& 演算子は、前後のオペランドの両方が true の場合に true になります。オペランドの一方、もしくは両方が false であれば、結果も false になります。

x == 0 && y == 0 // x と y の両方が 0 のときのみ true になる。

関係式は常に true または false になるので、&& 演算子自身も true または false を返します。関係演算子は、&&|| よりも優先順位が高いので、先ほどの式に括弧を記述する必要はありません。また、すべての値は true、または false に評価されるため、&& のオペランドは論理値以外でも問題ありません。

JavaScript では、論理値が必要な式や文であれば、true に評価される値や、false に評価される値に対しても問題なく動作しますので、&& 演算子が truefalse 以外の値を返しても、問題は生じません。

&& 演算子は、左側の式を先に評価します。左側の式の値が false に評価される値の場合は、式全体の値も false に評価される値になります。その時点で、&& 演算子は左側の式の値だけを返します。右側の式については評価を行いません。

逆に、左側の式の値が true に評価される値の場合、式全体の値は右側の式の値に依存します。右側の式が true に評価される値であれば、式全体の値は true に評価される値になります。逆に、右側の式が false に評価される値であれば、式全体の値は false に評価される値になります。したがって、左側の式の値が true に評価される値であれば、&& 演算子は右側の式を評価し、この値を返します。

var o = { x : 1 };
var p = null;
o && o.x // 1: o は true と評価されるので、戻り値は o.x。
p && p.x // null: p は false と評価されるので、戻り値は p。p.x は評価されない。

&& 演算子が、右側のオペランドをどのような場合に評価して、どのような場合に評価しないのかを理解しておくことは重要です。前述したコードでは、変数 pnull が設定されているので、p.x の式が評価されてしまうと、TypeError が発生します。しかし、このコードでは、&& をうまく使うことで、pnullundefined ではなく、true に評価される場合にのみ、p.x が評価されるようにしています。&& のこの振る舞いは、短縮表記と呼ばれることがあります。この振る舞いを意図的に使って、条件付きで実行するようなケースがあります。

if (a == b) stop(); // a==b の場合のみstop() を呼び出す。
(a == b) && stop(); // 上の文と同じ処理。

一般的に、&& の右側で、代入、インクリメント、デクリメント、関数呼び出しなどを持つような式を記述するときは注意が必要です。これらの右側の式が実行されるかは、左側の値によるからです。

論理和演算子

論理和演算子はオペランドの一方、または両方が true に変換される場合は、true に変換できる値を返します。両方のオペランドが false に変換される場合は、false を返します。

論理和演算子を単なる論理 OR 演算子ではありません。論理積演算子と同様に複雑な振る舞いをするので注意が必要です。まず、左側のオペランド (式) を評価します。この式の値が true に変換できる場合は、左側の式の値をそのまま変換せずに返します。

論理和演算子を使った以下のような慣用句がよく使われます。このような慣用句は、使用する値にいくつか候補があり、その候補が null ではない値 (true に評価される値) が使用されるようになっています。

// max_width 変数が定義されている場合は、この変数の値を使用する。
// 定義されていない場合は、preferences オブジェクトのプロパティを使用する。
// このプロパティも定義されていない場合は、プログラムに直接記述した定数(500)を使用する。
var max = max_width || preferences.max_width || 500;

この慣用句は、引数のデフォルト値を指定するために、関数本体中でよく使われます。

// o のプロパティを p にコピーして p を返す。
function copy(o, p) {
p = p || {}; // p にオブジェクトが渡されない場合、新たにオブジェクトを生成する。
// ここに関数を記述する。
}

論理否定演算子

論理否定演算子は単項演算子です。オペランドは 1 つしかありません。! をオペランドの前に置くと、そのオペランドの論理値を反転させます。例えば、変数 x の値が true と評価される値であれば、!xfalse になります。xfalse と評価される値であれば、!xtrue になります。

&& 演算子や || 演算子とは異なり、論理否定演算子はオペランドを論理値に変換した後、論理を反転させます。つまり、! 演算子が返す値は、truefalse だけです。したがって、変数 x に対して、!!x のように 2 つの論理否定演算子を前置すると、x に格納された任意の型の値を、対応する論理値に変換できます。

単項演算子として、! 演算子は高い優先順位を持ちます。p && q のような式の値を反転したい場合は、括弧を使って、!(p && q) のように記述しなければなりません。

// 任意の p と q の値に対して、以下の 2 つの等号が成り立ちます。
!(p && q) === !p || !q
!(p || q) === !p && !q

代入演算子

JavaScript では、= 演算子を使って、変数やプロパティに値を代入します。

i = 0 // 変数 i に 0 をセットする。
o.x = 1 // オブジェクト o のプロパティ x に 1 をセットする。

代入演算子の左側のオペランドは、変数、またはオブジェクトプロパティ(配列要素を含む) のいずれかの左辺値です。右側のオペランドは任意の型・任意の値になります。代入演算子を使用すると、左側の変数やプロパティには右側の値が代入され、それ以降の評価では右側の値になります。

代入演算子は、単純な式で使われる場合がほとんどですが、大きな式の一部として代入式の値が使われる場合があります。例えば、1 つの式で値を代入して比較もできます。

(a = b) == 0

このような式は、=== の 2 つの演算子の違いを理解した上で使用してください。代入演算子の優先順位は非常に低いので、大きな式の中で代入演算子の値を使う場合には、括弧を記述するのを忘れないようにしてください。代入演算子は、右から左に結合するので、式の中に複数の代入演算子を記述した場合は、右から左に評価されます。つまり、以下に示すように、1 つの値を複数の変数に代入することも可能です。

i = j = k = 0; // 3 つの変数を 0 に初期化する。

算術演算を伴う代入演算子

JavaScript は、通常の代入演算子のほかにも、いくつかの代入演算子をサポートしています。これらの演算子は、代入処理と他の算術演算を組み合わせたショートカットです。例えば、+= 演算子は加算と代入の 2 つの処理を実行します。

total += sales_tax

この式は次のように記述しても同じです。

total = total + sales_tax

+= 演算子は、数値と文字列の両方に適用できます。数値オペランドの場合は加算と代入が行われ、文字列オペランドの場合は連結と代入が行われます。ほかにも、-=*=&= などがあります。

JavaScript の演算子
演算子使用例意味
+=
-=
*=
/=
%=
<<=
>>=
>>>=
&=
|=
^=
a += b
a -= b
a *= b
a /= b
a %= b
a <<= b
a >>= b
a >>>= b
a &= b
a |= b
a ^= b
a = a += b
a = a -= b
a = a *= b
a = a /= b
a = a %= b
a = a <<= b
a = a >>= b
a = a >>>= b
a = a &= b
a = a |= b
a = a ^= b

使用例で示した式の場合は、式 a は一度しか評価されません。これに対し、意味で示した式の場合は、式 a は 2 回評価されます。a の中で、関数呼び出しやインクリメント演算子のような副作用がある場合は、前者と後者は異なる結果になります。例えば、次の2 つの代入式は異なる結果になります。

data[i++] *= 2;
data[i++] = data[i++] * 2;

評価式

多くのインタプリタ言語と同じように、JavaScript には、JavaScript のソースコード文字列を解釈する機能があります。そして、この文字列を評価することで値を生成します。JavaScript では、グローバル関数の eval() を使って評価します。

eval("3+2") // => 5

ソースコード文字列を動的に評価する機能は、プログラミング言語にとって強力な機能ですが、実際には、必要になる場面はほとんどありません。eval() を使う場合は、本当に使う必要があるのかを検討してみてください。

以降では、eval() の基本的な使い方について説明します。

eval()

eval() の引数は 1 つです。引数として文字列以外の値を渡した場合には、その値がそのまま返されます。文字列が渡された場合、文字列を JavaScript コードとして解釈します。解釈できない場合は、SyntaxError例外をスローします。文字列が解釈できた場合には、コードを評価し、文字列中の最後の式または文の値を返します。最後の式、または文が値を持たない場合には、undefined が返されます。文字列中から例外をスローする場合は、eval() はその例外をスローし直します。

eval() を呼び出すときに重要な点は、eval() を呼び出したコードの環境 (変数) が使われる点です。ある関数の中でローカル変数 x を定義しておき、eval("x") と呼び出すと、ローカル変数の値が得られます。eval("x=1") と呼び出せば、ローカル変数の値が変更されます。また、eval("var y = 3;") と呼び出せば、新たにローカル変数 y が宣言されます。同様に、以下のようなコードでローカル関数も宣言できます。

eval("function f() { return x+1; }");

eval() をトップレベルコードから呼び出した場合は、もちろん、グローバル変数やグローバル関数に対して処理を行うことになります。

eval() に渡すコード文字列は、構文上はその文字列で完結している点に注意してください。例えば、eval("return;") と記述しても return は関数中でしか意味がありません。評価する文字列が呼び出し元の関数と同じ変数環境を使うと説明しましたが、これは文字列自体が関数の一部になるという意味ではありません。なお、文字列が単独のスクリプトとして意味があれば、eval() の引数として問題ありません。それ以外の場合は、SyntaxError がスローされます。

グローバル eval()

eval() が JavaScript の最適化処理において問題となるのは、ローカル変数を変更できる機能です。そのため、この問題への対策として、インタプリタは eval() を呼び出す関数については最適化処理をあまり行わないようにしています。スクリプト中で eval() の別名を定義し、この別名を使って関数を呼び出している場合、JavaScript インタプリタは EvalError 例外をスローしてもよいことになっています。

eval() を別名で呼び出した場合、文字列をトップレベルのグローバルコードとして評価します。評価されたコードから、グローバル変数やグローバル関数を定義したり、グローバル変数を設定したりできますが、呼び出した関数のローカル変数を使ったり変更したりはできないようにしています。これにより、呼び出した関数に対する最適化処理には影響が出ないようになります。

ECMAScript 5 では、eval() の振る舞いを標準化しています。eval() 関数を "eval" の名前で呼び出すことを "直接 eval" と呼びます。直接 eval() を呼び出した場合は、呼び出しコンテキストの変数環境を使います。このほかの場合 ("間接呼び出し") は、変数環境としてグローバルオブジェクトを使います。ローカル変数や関数の読み書き、定義はできません。

var geval = eval;               // 別名を使うとグローバル eval になる。
var x = "global", y = "global"; // 2 つのグローバル変数。

function f() {                  // この関数はローカル eval を使う。
  var x = "local";              // ローカル変数を定義する。
  eval("x += 'changed';");      // 直接 eval ではローカル変数を設定する。
  return x;                     // 変更されたローカル変数が返される。
}

function g() {                  // この関数はグローバルeval を使う。
  var y = "local";              // ローカル変数を定義する。
  geval("y += 'changed';");     // 間接 eval では、グローバル変数を設定する。
  return y;                     // 変更されていないローカル変数が返される。
}

// ローカル変数が変更される。"localchanged global" が出力される。
console.log(f(), x);
// グローバル変数が変更される。"local globalchanged" が出力される。
console.log(g(), y);

IE9 よりも前のバージョンでは、IE の挙動はほかのブラウザと異なる挙動をします。eval() がほかの名前で呼び出されたとしても、グローバル eval を行いません。EvalError 例外もスローせず、ローカル eval を行うだけです。しかし、IE では、execScript() の名前のグローバル関数が定義されています。この execScript() を使えば、トップレベルスクリプトして、引数で指定した文字列を実行できます。ただし、eval() とは異なり、execScript() は常に null を返します。

strict モードでの eval()

ECMAScript 5 の strict モードでは、eval() 関数の振る舞いと "eval" の識別子の利用について制限をかけています。strict モードのコードから eval() が呼び出されたとき、または、"use strict" ディレクティブから始まるコード文字列が評価されたとき、eval() はプライベートな変数環境を使ってローカル eval を行います。つまり、strict モードでは、評価されたコードからはローカル変数を取得したり、変更したりできますが、ローカルスコープ中に新しい変数や関数を定義できません。

さらに、strict モードでは、eval() を演算子のように扱います。このため、"eval" を予約語にすることで、eval() 関数を新しい関数で上書きできないようにしています。"eval" の名前で、変数や関数、関数引数、キャッチブロックの引数を宣言することもできません。

条件演算子

条件演算子は、JavaScript で唯一の三項演算子です。三項演算子は、オペランドを 3 つ取ります。この演算子は "?:" と表記されますが、実際の使い方は少し違います。1 番目のオペランドの後ろに "?" を置き、その後ろに 2 番目のオペランド、その後ろに ":" を置いて、最後に 3 番目のオペランドを記述します。

x > 0 ? x : -x // x の絶対値。

条件演算子のオペランドには任意の型の値を指定できます。

  • 先頭のオペランドの値が true の場合は、2 番目のオペランドの値を評価し、その値を返します。
  • 先頭のオペランドの値が false の場合は、3 番目のオペランドの値を評価し、その値を返します。

2 番目のオペランドか、3 番目のオペランドのいずれかが評価され、両方が評価されることはありません。典型的な例は、変数が定義されているかどうか (true と評価される値を持つかどうか) をチェックし、定義されていればその値を使用し、定義されていなければデフォルトの値を使用します。

greeting = "hello " + (username ? username : "there");

これと同じことは if 文でも可能ですが、条件演算子を使用したほうが少ない行数で簡潔に記述できます。

greeting = "hello ";
if (username)
  greeting += username;
else
  greeting += "there";

typeof 演算子

typeof は単項演算子です。オペランドは 1 つだけです。オペランドの値は任意です。typeof 演算子は、オペランドのデータ型を示す文字列を返します。

typeof 演算子
xtypeof x
undefined
null
true または false
任意の数値または NaN
任意の文字列
任意の関数
任意の関数ではないネイティブオブジェクト
任意のホストオブジェクト
"undefined"
"object"
"boolean"
"number"
"string"
"function"
"object"
実装で定義された文字列。ただし、以下を除く
"undefined""boolean""number""string"

typeof 演算子の使用例を紹介しておきます。

(typeof value == "string") ? "'" + value + "'" : value

typeof 演算子は、switch 文と組み合わせて使った場合も便利です。なお、typeof 演算子のオペランドを指定するときに、括弧で囲んで指定できます。このように記述すると、typeof は、演算子のキーワードではなく、関数名のように見えるかもしれません。

typeof(i)

オペランドの値が null の場合も、typeof"object" の文字列を返すことに注意してください。null とオブジェクトを区別したい場合には、明示的に null かどうかをテストするようにしてください。

オペランドが関数以外のオブジェクトと配列の場合、typeof 演算子は "object" としか返さないので、オブジェクト型とそのほかの基本型とを区別することしかできません。どのオブジェクト型であるのかを知るには、instanceof 演算子、クラス属性、constructor プロパティを使います。

JavaScript では、関数はオブジェクトの一種ですが、関数は戻り値を持つので、ほかのオブジェクトとは異なると判断し、typeof 演算子は "function" の文字列を返します。

delete 演算子

delete 演算子は単項演算子で、オペランドに指定されたオブジェクトプロパティや配列要素を削除します。代入演算子、インクリメント演算子、デクリメント演算子のように、delete 演算子の値そのものではなく、プロパティを削除する副作用のために使われるのが一般的です。

var o = { x: 1, y: 2}; 
delete o.x;            // プロパティの 1 つを削除する。
"x" in o               // false: このプロパティは delete 済み。

var a = [1,2,3];       
delete a[2];           // 配列の最後の要素を削除する。
a.length               // 2: この時点では配列は要素を 2 つしか持たない。

プロパティが削除されると、プロパティは存在しなくなります。存在しないプロパティを読み出すと undefined が返されるので、in 演算子を使うことでプロパティが存在するかどうかをテストできます。

delete 演算子のオペランドには、左辺値を指定します。もしも左辺値ではない場合、delete 演算子は何も処理をせず、true を返します。左辺値を指定した場合は、delete 演算子は指定された左辺値を削除しようとします。指定された左辺値が削除できた場合には、delete 演算子は true を返します。

しかし、すべてのプロパティが削除できるわけではありません。コア言語やクライアントサイドの組み込みプロパティのいくつかは削除できないようになっています。var 文を使って宣言したユーザ定義の変数や、function 文で定義された関数、関数の仮引数についても削除できません。

ECMAScript 5 では、オペランドが変数、関数、関数の仮引数の場合、delete 演算子は SyntaxError をスローします。delete 演算子は、オペランドとしてプロパティアクセス式を指定したときにのみ動作します。strict モードでは、再定義不可 (nonconfigurable) のプロパティを削除しようとすると、delete 演算子は TypeError 例外を発生させます。非strict モードでは、前述のような場合にも例外は発生せず、delete 演算子は false を返し、オペランドが削除できなかったことを示すだけです。

var o = {x:1, y:2}; // 変数を宣言し、オブジェクトを代入する。
delete o.x;         // オブジェクトのプロパティの 1 つを削除。true が返される。
typeof o.x;         // プロパティはもう存在しないので "undefined" になる。
delete o.x;         // 存在しないプロパティを削除。true が返される。
delete o;           // 宣言された変数は削除できない。false が返される。
                    // strict モードでは例外が発生。
delete 1;           // 整数値は削除できない。true が返される。
this.x = 1;         // var を使わずにグローバルオブジェクトのプロパティを定義する。
delete x;           // strict モードでなければ、true が返される。
                    // strict モードでは例外が発生。
x;                  // x は定義されていないので、実行時エラーが発生する。

void 演算子

void は単項演算子です。オペランドとしては任意の値が取れます。ただし、void 演算子は、オペランド値を廃棄し未定義値を返す特殊なものであるため、あまり使われません。

実際に void 演算子を使用する場面は、クライアントサイド JavaScript の "javascript: URL" くらいです。式を評価する際の副作用は期待するが、式を評価した結果の値をブラウザが表示するのは望まない場合に使用します。HTML での使用例を以下に紹介しておきます。

Open New Window

HTML では、javascript: URL を使わなくても、onclick イベントハンドラを使えば、もっと明確に記述できます。イベントハンドラを使う場合は、もちろん void 演算子は必要ありません。

カンマ演算子

カンマ演算子は二項演算子です。オペランドには任意の型の値を指定できます。まず左側のオペランドを評価し、次に右側のオペランドを評価し、最後に右側のオペランドの値を返します。次の例を見てください。

i=0, j=1, k=2;

この式を評価すると 2 になります。上記のコードは次のように書いても同じです。

i = 0; j = 1; k = 2;

左側の式は評価されますが、値は捨てられます。つまり、カンマ演算子を使って意味があるのは、左側の式に副作用がある場合だけです。カンマ演算子が使われるのは、for ループで複数のループ変数を使うときが一般的です。

// 以下の例の最初のカンマは、var 文の構文の一部。
// 2 つ目のカンマがカンマ演算子。カンマ演算子で、
// 2 つの式 (i++ と j--)を 1 つの式しか受け付けない文 (for ループ)中に記述。
for(var i=0,j=10; i < j; i++,j--)
console.log(i+j);

英語の文がピリオドで終わるように、JavaScript の文はセミコロンで終わります。式は評価されると値が生成されるのに対して、文が実行されると何かを生じます。

何かを生じる方法の 1 つが、副作用を持つ式を評価することです。代入や関数呼び出しなどの副作用を持つ式は単独で文になりえます。このような文を 式文 と呼びます。同じような文としては、新たな変数を宣言したり、新たな関数を定義したりする宣言文があります。

JavaScriptインタプリタは、記述された順序で文を実行していきます。何かを生じるためのもう 1 つの方法は、デフォルトの実行順序を変更することです。JavaScript には、このような実行順序を変更するための文 (制御文)も、以下のようにいくつか用意されています。

  • if 文や switch 文などの条件文では、式の値に応じて文を実行したり、文を飛ばしたりします。
  • while 文や for 文などのループ文では、文を繰り返し実行します。
  • break 文や return 文、throw 文などのジャンプ文は、プログラムの別の部分にジャンプします。

式文

JavaScript で最も簡単な文は、副作用を伴う式です。代入文がその典型例です。

greeting = "Hello " + name;
i *= 3;

インクリメント演算子やデクリメント演算子を使った式も副作用を持ちます。この 2 つの演算子は、代入処理と同じように、副作用として変数の値を変更します。

counter++;

delete 演算子には、オブジェクトのプロパティを削除する重要な副作用があります。したがって、delete 演算子は、長い式の一部で使われるよりは、単独で式文として使われるのが普通です。

delete o.x;

関数呼び出しも式文の仲間です。

alert(greeting);
window.close();

複合文と空文

カンマ演算子を使って複数の式を 1 つの式にまとめたように、文ブロックを使えば、複数の文を 1 つの複合文にまとめられます。文ブロックは、複数のブロックを中括弧で囲んだものです。次の例は、1 つの文として扱われます。

{
  x = Math.PI;
  cx = Math.cos(x);
  console.log("cos(π) = " + cx);
}

この文ブロックについては注意すべき点がいくつかあります。まず、末尾にセミコロンを記述しません。ブロック内にある個々の文はすべて末尾にセミコロンを記述しますが、ブロック自身の末尾にはセミコロンは記述しません。次に、ブロック中の行は、中括弧に対してインデント(字下げ)されています。必ずしもインデントしなければならないわけではありませんが、インデントしておけば、コードは読みやすく、また理解しやすくなります。最後に、JavaScript にはブロックスコープがないことです。文ブロック中で宣言した変数は、ブロックの外からアクセスできないわけではありません。

JavaScript でのプログラミングにおいて、複数の文をまとめて文ブロックにすることはよくあります。式の中にほかの式が含まれるのが普通であるのと同じように、文の中にほかの文が含まれるのも普通です。このような場合、一般的に JavaScript の文法では、1 つの文しか記述できないようになっています。例えば、while ループの構文では、ループのボディとしては 1 つの文しか記述できません。文ブロックを使って複合文を記述すれば、任意の数の文を記述できます。

このように複合文を使うことで、JavaScript の文法上は1 つの文しか記述できない場所にも、複数の文を記述できるようになります。空文は、この反対です。1 つの文を記述する必要がある場所に、1 つも文を記述しないようにするものです。空文の書式は次のとおりです。

;

JavaScript インタプリタは、空文を実行しても何もしません。空文は、本体が何もないループなどで役立つことがあります。次のfor ループの例を見てください。

// 配列a を初期化する。
for(i = 0; i < a.length; a[i++] = 0) ;

このループでは、すべての処理は a[i++] = 0 で行われるので、ループのボディ部は必要ありません。しかし、JavaScript の構文では、ループのボディ部として文が 1 つ必要ですので空文が使われています。for ループ (または while ループや if 文) の閉じ括弧の後ろにセミコロンを記述すると、原因をなかなか見つけにくいバグが生じることがあるので注意してください。


if ((a == 0) || (b == 0)); // この行は何も実行しない。
o = null;                  // この行は常に実行される。

意図的に空文を使用する場合は、その旨をコメントしておくことを推奨します。例えば、次のようにコメントするとよいでしょう。

for(i=0; i < a.length; a[i++] = 0) /* 空文 */ ;

宣言文

var 文と function 文が宣言文です。それぞれ変数の宣言と関数の定義を行います。この 2 つの文は、プログラムのほかの場所で使えるように識別子 (変数名と関数名) を定義します。そして、これらの識別子を使って値を代入できるようにします。宣言文は変数や関数を生成することで、プログラム中のほかの文のために準備を行う重要な役目を果たしています。

var 文

1 つまたは複数の変数を明示的に宣言するときに、var 文を使用します。書式は以下のとおりです。

var name_1 [= value_1 ] [, ..., name_n [= value_n ]]

先頭にキーワードvar、その次に変数名を記述します。変数が複数個ある場合は、カンマ(,)で区切って記述します。それぞれの変数ごとに初期値を指定できます。初期値は、代入演算子(=)の後ろに初期化式で指定できます。

var i;                                      // 変数 1 つ。
var j = 0;                                  // 変数 1 つに値も設定。
var p, q;                                   // 変数 2 つ。
var greeting = "hello" + name;              // 複雑な初期化子。
var x = 2.34, y = Math.cos(0.75), r, theta; // たくさんの変数。
var x = 2, y = x*x;              // 2 つ目の変数で、1 つ目の変数を使用。
var x = 2,                       // 複数の変数を、変数ごとに行を分けて記述。
f = function(x) { return x*x },
y = f(x);

関数の本体で var 文を記述した場合、ローカル変数を定義することになります。このローカル変数のスコープはその関数内になります。トップレベルコードで var を使った場合、グローバル変数を宣言することになります。グローバル変数は JavaScript プログラム全体を通してアクセスできます。グローバル変数はグローバルオブジェクトのプロパティになります。しかし、ほかのグローバルプロパティと異なり、delete 文を使って生成したプロパティは削除できません。var 文で変数に初期化子を指定しなかった場合、その初期値は未定義値になります。

なお、for ループや for/in ループの一部に var 文を記述もできます。

for(var i = 0; i < 10; i++) console.log(i);
for(var i = 0, j=10; i < 10; i++,j--) console.log(i*j);
for(var i in o) console.log(i);

同じ変数を複数回宣言しても問題ありません。

function 文

関数宣言文の文形式でも関数は定義できます。関数宣言文でも、function キーワードを使って、関数を定義します。次に 2 つの関数の例を示します。


var f = function(x) { return x+1; } // 変数に代入する式。
function f(x) { return x+1; }       // 関数名を含む文。

// 関数宣言文の書式は次のとおり。
function funcname ([arg_1 [, arg_2 [..., arg_n ]]]) {
  statements
}

funcname に指定した識別子が、宣言する関数の名前になります。関数名の後ろの括弧内に仮引数を指定します。仮引数はあってもなくてもかまいません。仮引数が複数個ある場合は、"," で区切って指定します。関数の本体で、この仮引数名を使用できます。関数の呼び出し時に指定された値が、この仮引数に代入されます。

関数の本体には、文をいくつでも記述できます。ここで記述された文は、関数の定義時に実行されるわけではありません。これらの文は、新たに生成される関数オブジェクトに関連付けられて、関数が呼び出されたときに実行されます。なお、関数文では中括弧が必須であることに注意してください。while ループなどで使用される文ブロックとは異なり、関数の本体を構成する文が 1 個しかない場合でも、その文を中括弧で囲まなくてはなりません。

function hypotenuse(x, y) {
  return Math.sqrt(x*x + y*y);
}

// 再帰関数。
function factorial(n) {
  if (n <= 1) return 1;
  return n * factorial(n - 1);
}

関数はトップレベルで定義することも、別の関数中に入れ子にして定義もできます。ただし、入れ子にする場合も、関数の "トップレベル" で定義するようにしてください。if 文や while ループなど、ほかの文の一部として定義することはできません。

関数宣言文には関数名が含まれます。この点が関数定義式と異なる点です。関数宣言文も、関数定義式も、新しい関数オブジェクトを生成します。しかし、関数宣言文では、関数名として指定された名前を持つ変数も同時に宣言し、この変数に関数オブジェクトを代入します。var 文で宣言した変数のように、関数定義文で定義された関数は、暗黙的にこの関数を含むスクリプトや関数の先頭に "巻き上げ" られます。したがって、定義された関数は、スクリプト全体や、関数全体からアクセスできます。var 文の場合は、変数宣言だけが巻き上げられ、変数の初期化は、var 文を記述した場所で行われます。しかし、関数宣言文の場合は、関数名と関数本体が巻き上げられます。スクリプト中の関数や、関数中に入れ子にされた関数は、ほかのコードが実行される前にすべて宣言された状態になります。つまり、JavaScript では、関数宣言文よりも前のコードから関数を呼び出せます。

var 文と同じように、関数宣言文で生成した変数は削除できません。しかし、これらの変数は読み出しのみではなく、上書きできます。

条件文

条件文は、指定された式の値に応じて、ほかの文を実行したり、飛ばしたりします。条件文はコードの流れの分かれ目であり、分岐と呼ばれることもあります。条件文は、コードの流れが 2 つ以上に分岐する部分にあたります。インタプリタはどの分岐を選ぶかを決定しなければなりません。

if文

if 文は条件を判定し、条件付きで文を実行するための基本的な制御文です。if 文には 2 つの書式があります。if 文の第一の書式は次のとおりです。

if (expression)
  statement

この書式では、まず expression を評価し、その結果が true と評価できる場合に statement を実行します。式の評価結果が false と評価できる場合には statement を実行しません。

if (username == null)    // username が null または未定義の場合
  username = "John Doe"; // 定義する。

あるいは次のように記述もできます。

// username が null、未定義値、false、0、""、NaN の場合、新たに値を設定する。
if (!username) username = "John Doe";

式を囲んでいる丸括弧は、if 文の書式の一部として必ず記述しなければならいことに注意してください。JavaScript の構文では、if キーワードの後に、式を丸括弧で囲んで記述し、その後に文を 1 つだけ記述します。しかし、文ブロックを使って複数の文を 1 つにまとめることで、複数の文を記述できます。

if (!address) {
  address = "";
  message = "Please specify a mailing address.";
}

if 文のもう 1 つの書式は、else 句を含むものです。式の評価結果が false の場合に、else 句が実行されます。第二の書式は次のとおりです。

if (expression)
  statement1
else
  statement2

この書式の場合、expression の評価結果が true と評価される場合は statement1 を実行し、それ以外の場合は statement2 を実行します。実例を以下に示します。

if (n == 1)
console.log("You have 1 new message.");
else
console.log("You have " + n + " new messages.");

else 句を伴う if 文を入れ子にする場合は、else 句と if 文の対応関係に十分気を配る必要があります。

i = j = 1;
k = 2;

if (i == j)
  if (j == k)
    console.log("i equals k");
else
  console.log("i doesn't equal j"); // 誤り

この例には、if 文が 2 つあります。どちらの if 文が else 句に対応するのでしょうか。インデントを見れば、先頭 (外側) の if 文のつもりであることがわかりますが、JavaScript は次のように解釈します。

if (i == j) {
  if (j == k)
    console.log("i equals k");
  else
    console.log("i doesn't equal j"); // 誤り
}

JavaScript の場合、else 句は "直近の if 文" に対応します。この規則は、ほかのプログラミング言語でもだいたい同じです。中括弧を使って次のように記述すれば、対応関係が明解になります。これなら誤解もないし、コードの保守やデバッグも容易です。

if (i == j) {
  if (j == k) {
    console.log("i equals k");
  }
}
else {
  console.log("i doesn't equal j");
}

else if 文

条件の判定結果に基づいて 2 つのコードのどちらか一方を実行するには、if/else 文が便利なことがわかりました。しかし、実行するコードが 3 個以上ある場合はどうしたらよいのでしょうか。1 つのやり方は、else if 文を使用することです。

if (n == 1) {
  // コードブロック #1 を実行する。
}
else if (n == 2) {
  // コードブロック #2 を実行する。
}
else if (n == 3) {
  // コードブロック #3 を実行する。
}
else {
  // すべての else が成立しないときには、コードブロック #4 を実行する。
}

このコードには、特別なものは何もありません。一連の if 文があり、それぞれの if 文が前の文の else 句の一部になっているだけです。入れ子を利用した従来のコードを以下に紹介しますが、このやり方より else if 文のほうがすっきりします。

if (n == 1) {
  // コードブロック #1 を実行する。
}
else {
  if (n == 2) {
    // コードブロック #2 を実行する。
  }
  else {
    if (n == 3) {
      // コードブロック #3 を実行する。
    }
    else {
      // すべてのelse が成立しないときには、コードブロック #4 を実行する。
    }
  }
}

switch 文

if 文は、プログラムの実行の流れを分岐させるものです。また、else if 文を使って多重分岐させることもできます。しかし、分岐条件で使用する式が1 つだけの場合、このやり方は必ずしもベストだとは言えません。複数の if 文で同じ式を何回も評価するのは無駄です。

このような問題を解決するために switch 文が用意されています。先頭にキーワード switch を記述し、その後ろに、丸括弧で囲んで式 (expression) を、そして、中括弧に囲んで一連の文 (statements) からなるコードブロックを記述します。

switch( expression ) {
  statements
}

switch 文の完全な書式はもっと複雑になります。コードブロック内のさまざまな場所に、"case expression :" の書式の case ラベルを記述します。case 文はラベル文と似ています。ただし、ラベル文では文に名前 (ラベル) を付けますが、case 文では文に式を関連付けます。switch 文が実行されると、expression の値を求め、その値に一致する case ラベルを探します (一致するかどうかの判定は、=== 演算子と同じ比較で判定します)。一致する case ラベルがあれば、そのcase ラベルの後ろに続く最初の文からコードブロックを実行します。一致する case ラベルがなければ、"default:" の特別なラベルを探し、もしあれば、この default: ラベルの後ろに続く最初の文からコードブロックを実行します。一致する case ラベルも default: ラベルもなければ、そのコードブロックをスキップします。

switch(n) {
  case 1:  // n == 1 の場合、ここから開始する。
    // コードブロック#1 を実行する。
    break; // ここで中断する。
  case 2:  // n == 2 の場合、ここから開始する。
    // コードブロック#2 を実行する。
    break; // ここで中断する。
  case 3:  // n == 3 の場合、ここから開始する。
    // コードブロック#3 を実行する。
    break; // ここで中断する。
  default: // すべてのelse が成立しない場合、
    // コードブロック#4 を実行する。
    break; // ここで中断する。
}

上記のコードで、それぞれの case 句の最後にキーワード break があることに注意してください。break 文が実行されると、switch 文を終了します。switch 文の case ラベルは、実行すべきコードの開始点を示すだけで、終了点は示していません。したがって、break 文を省略すると、expression の値に一致した case ラベルの後ろにあるコードブロックから処理を開始し、コードブロックの終わりまで一連の文を処理していきます。しかし、通常は switch 文の各 case キーワードの最後に break 文を記述して終了点を明確にします。なお、break 文の代わりに return 文が使え、どちらでも switch 文を終了できます。以下は、値の型に応じて値を文字列に変換する例です。

function convert(x) {
  switch(typeof x) {
    case 'number':   // 数値を16 進数に変換して返す。
      return x.toString(16);
    case 'string':   // 文字列を引用符で囲んで返す。
      return '"' + x + '"';
    default:         // 上記以外の型の場合、通常の方法で変換する。
      return String(x);
  }
}

上記の例では、case ラベルに、数値リテラルと文字列リテラルを記述していますが、ECMAScript 標準では任意の式が記述できるようになっています。

switch 文は、switch キーワード直後の式を評価した後に、case ラベルの式を評価します。値が一致する case ラベルが見つかるまで、コードに記述された順に case ラベルの式を評価していきます。値は、等値演算子ではなく、同値演算子を使って比較されるため、型変換が行われないことに注意して下さい。

switch 文が実行されるときに、すべての case 式が評価されるわけではないことに注意して下さい。そのため、case 式に関数呼び出しや代入のような副作用を持つような式を書くのは避けたほうがよいでしょう。また、case ラベルの式には、できるだけ定数を書くようにすると複雑化しにくくなります。

switch 文の expression に一致する case ラベルが存在しない場合、switch 文は default ラベルを実行します。もしも default ラベルが存在しない場合は、switch 文のブロック全体を実行しません。上記の例では、default ラベルは switch 文の最後に記述していますが、switch 文のコードブロック中であれば、どこに記述してもかまいません。ただし、通常は一番最後に記述するのが一般的です。

ループ文

ループ文は、JavaScript のインタプリタがソースコードを戻ってある部分を繰り返します。JavaScript には "while 文"、"do/while 文"、"for 文"、"for/in 文" の 4 つのループ文があります。。

while 文

JavaScript で条件判定の基本的な制御文が if 文であるとすれば、 JavaScript で繰り返し処理の基本的な制御文は while 文です。while 文の書式は次のとおりです。

while ( expression )
  statement

while 文を実行すると、まず expression を評価します。その結果が false と評価される場合は、ループ本体の文を飛ばして次の文の処理へ進みます。評価結果が true と評価される場合は、statement を実行し、それから元の expression の評価に戻ります。つまり、式が true と評価される間は、インタプリタは statement を繰り返し実行します。なお、while(true) とすると、無限ループになることに注意しましょう。

一般的に無限ループするプログラムを組み上げることはありません。無限ループしないために、ループを繰り返すたびに変化する変数を利用します。変数の値が変化すれば、実行する内容も毎回変わります。変化する変数が expression に含まれていれば、ループを繰り返すたびに expression の値も変わります。これは非常に重要なことで、もし expressiontrue と評価される値で始まり、そのまま何も変わらなければ、ループも終わらなくなってしまいます。

var count = 0;
while (count < 10) {
  console.log(count);
  count++;
}

上記のコードでは、変数count の値は 0 から始まります。ループの本体を実行するたびに、count の値が 1 つ増えます。そのため、ループを 10 回繰り返すと、count の値は 10 になるので、while 文の式の評価結果は false になります。その時点で while 文の処理は終了し、JavaScript インタプリタはプログラムの次の文へと処理を進めます。

このようにほとんどのループでは、count のようなカウンタ変数を使用します。ループカウンタの変数にはどのような名前を付けてもかまいませんが、一般には ijk がよく使われます。

do/while 文

do/while ループは while ループとよく似ていますが、expression がループの先頭ではなく、最後にある点が異なります。つまり、do/while 文の場合、ループ内の式が少なくとも 1 回は必ず実行されます。do/while 文の書式は次のとおりです。

do
  statement
while ( expression );

しかし、do/while ループが while ループほど使われないのは、少なくとも1 回実行するループを必要とする状況があまり多くないからです。以下は、do/while ループを使った例です。

function printArray(a) {
  var len = a.length, i = 0;
  if (len == 0)
    console.log("Empty Array");
  else {
    do {
      console.log(a[i]);
    } while (++i < len);
  }
}

do/while ループと通常の while ループには以下の点が異なります。

  • ループの開始を示すキーワード "do" と、ループの終了とループ条件を記述するキーワード "while" が必要。
  • 最後に終端子 ";" を書く必要がある。

for 文

一般的に、ループには以下のような共通パターンがあります。

  • ループには何らかのカウンタ変数があり、ループを開始する前にこの変数を初期化する。
  • この変数をテストしてから、ループの繰り返し処理を行う。
  • ループ本体の末尾でカウンタ変数を更新してから、再び変数をテストする

for 文を使えば、このようなパターンのループを簡単に記述できます。for 文では、初期化、テスト、更新のループ変数の重要な要素を明示的にまとめて記述します。for 文の書式は次のとおりです。

for( initialize ; test ; increment )
  statement

initializetestincrement は、それぞれ初期化、テスト、更新を行う式です。これらの 3 つの式はセミコロンで区切ります。for 文では、この 3 つの処理をまとめて、ループの先頭に明示的に記述します。このやり方なら、ループで何をしているのかがよくわかり、ループ変数の初期化や更新を忘れるなどの誤りを防げます。for 文の働きは、for 文と同じ処理を行う次の while 文と比較するとよくわかると思います。

initialize ;
while( test ) {
  statement
  increment ;
}

ループを開始する前に、initialize 式を 1 回だけ評価します。通常、initialize 式は代入などの副作用を伴います。var 文を使って変数を宣言もできます。ループカウンタ変数の宣言と初期化が同時にできて便利です。

繰り返しの前に test を実行し、その結果に基づいてループ本体を実行するかどうかを決めます。test の評価結果が true と評価される場合は、ループ本体の statement を実行します。

最後に、increment 式を評価します。increment 式も副作用を伴うものでなければなりません。一般的に increment 式は代入式、またはインクリメント演算子・デクリメント演算子を使った式になります。

先ほどの while ループを使って 0 から 9 を出力するコードを、for 文を使って書き直したものが以下になります。

for(var count = 0; count < 10; count++)
console.log(count);

複雑なループでは、ループの繰り返し処理で変化する変数が複数個ある場合もあります。変数が複数個ある場合は、カンマ演算子を使用します。カンマ演算子を使うと、initializeincrement などで複数の式を 1 つの式にまとめ、for ループで複数の式を記述できるようになります。

var i, j;
for(i = 0, j = 10 ; i < 10 ; i++, j--)
sum += i * j;

ここまで挙げた例のループ変数はすべて数値でした。ループ変数として数値が使われることは多いのですが、数値でなければならないわけではありません。例えば、以下のコードでは、ループを使ってリンクリスト型のデータ構造をたどり、リストの最後のオブジェクトを返します。

function tail(o) {                      // リンクリスト o の末尾を返す。
  for(; o.next; o = o.next) /* 空文 */ ; // o.next が true と評価される間たどる。
  return o;
}

上記のコードには initialize 式がありませんが 3 つの式はどれも省略してもかまいません。ただし、式を省略しても 2 つのセミコロンは必ず記述する必要があります。test 式を省略した場合は、無限ループになります。for(;;)while(true) と同じ意味になり、無限ループになります。

for/in 文

for/in 文では for キーワードを使います。しかし、for/in 文は、通常の for ループとは異なるループ処理を行います。書式は次のとおりです。

for (variable in object )
  statement

variable には変数を記述するのが一般的ですが、左辺値と評価される式や、var 文も記述できます。つまり、代入式の左側として適切なものを記述します。object は、評価するとオブジェクトになる式です。statement は、ループの本体を形成する文または文ブロックです。

for ループ文は、配列の要素を巡回するときによく使われます。

for(var i = 0; i < a.length; i++) // 変数i に配列のインデックスを代入する。
  console.log(a[i]);              // 配列の各要素の値を出力する。

これに対して for/in ループ文は、オブジェクトのプロパティを巡回するときによく使われます。

for(var p in o)      // o のプロパティ名を変数p に代入する。
  console.log(o[p]); // 各プロパティの値を出力する。

for/in 文を実行すると、JavaScript インタプリタは、まず object 式を評価します。評価結果が nullundefined の場合、インタプリタはループ文をスキップし、ループ文の次の文に移動します。object 式の値が基本型の場合、その値をラッパーオブジェクトに変換します。次に、オブジェクトの列挙可能なプロパティごとに、ループ本体を一度ずつ実行します。ただし、ループ本体を実行する前に、毎回 variable 式を評価し、プロパティの名前 (文字列値) を代入します。

for/in ループの variable には、評価すると代入式の左側としてふさわしい値になる式であれば、どんな式でも記述できます。ループを実行するたびに、この式は評価されます。つまり、ループを実行するたびに違う値にすることも可能です。例えば、次のようなコードにより、オブジェクトのプロパティの名前をすべて配列にコピーできます。

var o = {x:1, y:2, z:3};
var a = [], i = 0;
for(a[i++] in o) /* 空文 */;

JavaScript では、配列はオブジェクトの一種です。したがって、for/in ループでは、オブジェクトのプロパティだけでなく、配列のインデックスも変数に代入されます。例えば、先ほどの例の後に次のコードを記述すると、配列のインデックスである0、1、2 が出力されます。

for(i in a) console.log(i);

実際には、for/in ループ文で、オブジェクトのすべてのメソッドや、プロパティが調べられるわけではありません。列挙不可なプロパティ、およびコア JavaScript 言語で定義されている組み込みメソッドは調べることができません。例えば、すべてのオブジェクトは toString() メソッドを持ちますが、for/in ループでは toString プロパティは調べられません。コード中で定義されたプロパティ、メソッド、およびユーザ定義の継承プロパティは、for/in ループで調べられます。

for/in ループのループ本体でまだ変数に代入されていないプロパティを削除した場合、そのプロパティは変数に代入されません。ループ本体で新たにプロパティを定義した場合、そのプロパティが変数に代入されないのが一般的です。

プロパティの列挙順序

ECMAScript 仕様では、for/in ループでオブジェクトのプロパティが代入されていく順序については規定していません。しかし、実際にはオブジェクトのプロパティが定義された順で、プロパティを調べることができます。オブジェクトがオブジェクトリテラル形式で作成されたのであれば、プロパティを調べる順序は、リテラル中で記述した順序になります。Web サイトやライブラリによっては、この順序に依存したものがあります。そのため、ブラウザベンダーは今後もこのような動作を変更しないものと考えられます。

ここまでのところで説明したのは、単純なオブジェクトでのプロパティの列挙順序についてです。この順序については、ブラウザ間でも互換性が高いものになっています。しかし、以下のような場合には、順序は処理系依存となり、可搬性は低くなります。

  • オブジェクトが、列挙可能なプロパティを継承している。
  • オブジェクトのプロパティに、配列インデックスが含まれている。
  • オブジェクトの既存のプロパティを削除している。
  • Object.defineProperty() などを使って、オブジェクトのプロパティ属性を変更している。

継承プロパティは、オブジェクト自身のプロパティの後に、変数に代入されていくのが一般的です。オブジェクトが 2 つ以上の "プロトタイプ" からプロパティを継承する場合 ("プロトタイプチェーン" にオブジェクトが複数ある場合)、プロトタイプチェーン中のプロトタイプオブジェクトごとに作成順でプロパティが代入されていきます。1 つのプロトタイプオブジェクトのプロパティをすべて調べたら、その次のプロトタイプオブジェクトに移ります。ブラウザの処理系によっては、配列のプロパティを "作成順" ではなく、"数値順" で調べるものもあります。また、配列の中に数値以外のプロパティが含まれていたり、配列のインデックスが飛んでいたりすると、作成順に動作を変更します。

ジャンプ文

JavaScript には、ジャンプ文 と呼ばれる文があります。ジャンプ文を実行すると、JavaScript インタプリタはソースコードの別の場所にジャンプします。

例えば、break 文を実行すると、JavaScript インタプリタはループ文などの次の文にジャンプします。continue 文を実行すると、ループ文の本体をスキップし、ループ文の先頭に戻り、新たなループを実行します。また、文には名前 (ラベル) を付けることができます。このラベルを使って、break 文や continue 文で、どの文を対象にしているかを指定できます。

ラベル文

先頭に識別子(identifier)、その後ろにコロン(:)、最後に文を記述することで、任意の文にラベルを付けることができます。ラベル文の書式は次のとおりです。

identifier : statement

文にラベルを付けておけば、プログラムの他の場所からラベル名でその文を参照できます。どの文にもラベルを付けることはできますが、一般的にはループ文や条件文などの本体部分を持つ文にラベルを付けます。ループ文にラベルを付けておけば、break 文を使ってループを抜け出したり、continue 文を使ってループの先頭に戻り次のループを開始したりできます。文ラベルを使う JavaScript 文は、break 文と continue 文だけです。次の例では、while ループにラベルを付けて、continue 文でこのラベルを使っています。

mainloop: while(token != null) {
  // コード省略。
  continue mainloop; // 名前を付けたループ文の次のループを開始する。
  // コード省略。
}

identifier には、JavaScript で有効な識別子 (予約語を除く) をラベル名として指定します。ラベル名の名前空間は、変数名や関数名の名前空間と異なります。そのため、文ラベルには変数名や関数名と同じ識別子が使えます。

break 文

break 文がラベルなしで実行されると最も内側のループ、または switch 文を直ちに終了します。break 文はループ文や switch 文を終了させるものなので、これらの文の中でしか使えません。

何らかの理由でループの処理を途中で止めたいときに break 文を使用します。ループの終了条件が複雑になる場合、それらの条件をすべて 1 つのループ式内に記述するより、break 文を使用したほうが簡単です。

以下は、配列要素の中から特定の値を求めるコード例です。。ループの処理は、配列の最後までループを繰り返せば処理を終了します。しかし、以下の例では求める値が見つかった時点で break 文により処理を終了します。

for(var i = 0; i < a.length; i++) {
  if (a[i] == target) break;
}

また、キーワード break の後ろにラベル名を指定もできます。ラベル名は識別子だけを記述し、コロンは記述しません。この場合の書式は次のとおりです。

break labelname ;

ラベルが指定された break 文が実行されると、ラベルで指定された文の最後に処理が移動し、文が終了します。break 文を囲む文の中に、指定されたラベルを持つ文がない場合に、この形式で break 文を使うと構文エラーになります。break 文を含む文であれば、どれでもラベルに指定できます。ループや switch 文である必要はありません。例えば、複数の文を中括弧 ({}) で囲んだ文ブロックのラベルを指定することも可能です。キーワード break とラベル名の間に改行を入れないように注意してください。改行を入れると、JavaScript はセミコロンが省略されていると解釈して、自動的にセミコロンを挿入します。その結果、ラベルなしの break 文になってしまいます。入れ子型のループや switch 文を使用する場合、最も内側の文以外の文を終了させるにはラベル付きの break 文を使います。例を紹介します。

// 数値の 2 次元配列を取得する。
var matrix = getData();
// 行列中の数字をすべて加算する。
var sum = 0, success = false;
// ラベル文を使って、エラーが発生したときに抜け出す。
compute_sum: if (matrix) {
  for(var x = 0; x < matrix.length; x++) {
    var row = matrix[x];
    if (!row) break compute_sum;
    for(var y = 0; y < row.length; y++) {
      var cell = row[y];
      if (isNaN(cell)) break compute_sum;
      sum += cell;
    }
  }
  success = true;
}
// break 文はここにジャンプする。
// success==false の状態でここにたどりついたら、行列中に何か問題が発生している。
// false でなければ、変数 sum には、行列中のすべてのセルの合計値が含まれている。

ラベルの有無に関わらず、break 文では、関数の境界は越えられません。例えば、関数の定義文にラベルを付けて、関数中からこのラベルを使うことはできません。

continue 文

continue 文は、break 文とよく似ています。break 文はループを終了しますが、continue 文は次の繰り返しからループの処理を再開します。以下に示すように、ラベルを指定もできます。

continue labelname ;

ラベルの有無に関わらず、continue 文はループの中でしか使えません。それ以外の場所で使用すると、構文エラーになります。

continue 文が実行されると、ループ内で現在実行中の繰り返し処理は中断され、次の繰り返しから処理を続行します。この定義は、ループの種類によって意味が異なります。

  • while ループの場合は、ループの先頭で指定された expression 式が再テストされ、その結果が true であれば、ループ本体を初めから実行します。
  • do/while ループの場合は、途中を飛ばしてループの最後にいき、そこに指定されたループ条件が再テストされ、その結果に応じてループの先頭から処理が再開されます。
  • for ループの場合は、ループ変数を更新する increment 式を評価してから test 式を再テストし、次の繰り返し処理へ進むかどうかが判定されます。
  • for/in ループの場合は、指定された変数に代入された次のプロパティ名から処理が再開されます。

continue 文の振る舞いが、while ループと for ループで異なることに注意してください。while ループの場合は条件へ直接戻りますが、for ループの場合は increment 式を評価してから条件へ戻ります。エラーが発生したときにループ内で現在処理中の繰り返しを中断させる、ラベル指定なしの continue 文の例は以下になります。

for(i = 0; i < data.length; i++) {
  if (!data[i]) continue; // 未定義の値に対しては処理できない。
  total += data[i];
}

break 文と同様、continue 文でもラベル付きの書式が使えます。入れ子になったループで再開すべきループが最も内側のループでない場合は、ラベル指定の書式を使います。キーワード continue とラベル名との間に改行を入れないようにします。これは、ラベル指定の break 文の場合と同じ注意です。

return 文

関数中で return 文を使うことで、関数呼び出しの値を指定します。return 文の書式は次のとおりです。

return expression ;

return 文は、関数の本体でしか使えません。それ以外の場所で使用すると、構文エラーになります。return 文を実行すると、この return 文を含む関数は、式の値を呼び出し元に返します。

function square(x) { return x*x; } // return 文を持つ関数。
square(2) // この呼び出しは4 と評価される。

return 文が関数中に含まれない場合、関数を呼び出すと、関数本体中の文を次々に実行していき、関数の最後に到達すると呼び出し元に戻ります。このような場合には、呼び出し式の値はundefined と評価されます。return 文は多くの場合、関数の最後の文として記述されます。ただし、最後でなければならないわけではありません。return 文を実行すると、以降に文が残っていたとしても、関数は呼び出し元に戻ります。return 文に式を指定しなくてもかまいません。この場合、undefined が呼び出し元に返されます。次の例を見てください。

function display_object(o) {
// 引数がnull またはundefined の場合、すぐに戻る。
if (!o) return;
// 関数の本体を記述する。
}

キーワードreturn と式の間に改行を入れないようにしてください。改行を入れると、JavaScript はセミコロンが省略されていると解釈し、自動的に余計なセミコロンを挿入してしまいます。

Category:
プログラミング
公開日:
更新日:
Pageviews:
107
Shares:
5
Tag:
JavaScript
hatebu icon
hatebu