この章では、「基本JavaScript」について説明します。これは、私が選んだJavaScriptのサブセットの名前で、できる限り簡潔でありながら、生産性を維持できるように設計されています。JavaScriptの学習を始めたばかりの方は、言語の残りの部分に進む前に、しばらくの間このサブセットでプログラミングすることをお勧めします。そうすれば、すべてを一度に学ぶ必要がなく、混乱せずに済みます。
このセクションでは、JavaScriptがなぜこのようになっているのかを理解するのに役立つ、JavaScriptの背景について少し説明します。
ECMAScriptは、JavaScriptの公式名称です。 JavaScript(もともとはSunが、現在はOracleが所有)に商標があるため、新しい名前が必要になりました。 現在、Mozillaは、以前にライセンスを取得したため、公式にJavaScriptという名前を使用することを許可されている数少ない企業の1つです。 一般的な使用法では、これらの規則が適用されます。
JavaScriptの作成者であるBrendan Eichは、非常に迅速に言語を作成せざるを得ませんでした(さもなければ、より悪いテクノロジーがNetscapeに採用されていたでしょう)。彼はいくつかのプログラミング言語から借用しました。Java(構文、プリミティブ値とオブジェクト)、SchemeとAWK(ファーストクラス関数)、Self(プロトタイプ継承)、PerlとPython(文字列、配列、正規表現)です。
JavaScriptには、ECMAScript 3まで例外処理がありませんでした。そのため、言語が非常に頻繁に値を自動的に変換し、非常に頻繁に暗黙のうちに失敗します。最初は例外をスローできなかったからです。
一方、JavaScriptには癖があり、機能がかなり欠落しています(ブロックスコープの変数、モジュール、サブクラス化のサポートなど)。一方、これらの問題を回避できる強力な機能がいくつかあります。他の言語では、言語機能を学習します。JavaScriptでは、代わりにパターンを学習することがよくあります。
その影響を考えると、JavaScriptが関数型プログラミング(高階関数、組み込みのmap
、reduce
など)とオブジェクト指向プログラミング(オブジェクト、継承)の混合であるプログラミングスタイルを可能にするのも不思議ではありません。
このセクションでは、JavaScriptの基本的な構文原則について説明します。
// Two slashes start single-line comments
var
x
;
// declaring a variable
x
=
3
+
y
;
// assigning a value to the variable `x`
foo
(
x
,
y
);
// calling function `foo` with parameters `x` and `y`
obj
.
bar
(
3
);
// calling method `bar` of object `obj`
// A conditional statement
if
(
x
===
0
)
{
// Is `x` equal to zero?
x
=
123
;
}
// Defining function `baz` with parameters `a` and `b`
function
baz
(
a
,
b
)
{
return
a
+
b
;
}
=
)は、変数に値を代入するために使用されます。===
)は、2つの値を比較するために使用されます(等価演算子を参照)。JavaScriptの構文を理解するには、主要な2つの構文カテゴリ、ステートメントと式があることを知っておく必要があります。
ステートメントは「何かを実行」します。プログラムは一連のステートメントです。以下は、変数foo
を宣言(作成)するステートメントの例です。
var
foo
;
式は値を生成します。式は、関数引数、代入の右辺などです。以下は式の例です。
3
*
7
ステートメントと式の区別は、JavaScriptにif-then-else
を実行する2つの異なる方法があることからもよくわかります。ステートメントとして:
var
x
;
if
(
y
>=
0
)
{
x
=
y
;
}
else
{
x
=
-
y
;
}
または式として
var
x
=
y
>=
0
?
y
:
-
y
;
後者は関数引数として使用できます(ただし前者は使用できません)
myFunction
(
y
>=
0
?
y
:
-
y
)
最後に、JavaScriptがステートメントを期待する場所であればどこでも、式を使用することもできます。たとえば
foo
(
7
,
1
);
行全体がステートメント(いわゆる式ステートメント)ですが、関数呼び出しfoo(7, 1)
は式です。
JavaScriptではセミコロンはオプションです。ただし、そうしないとJavaScriptがステートメントの終わりを誤って推測する可能性があるため、常にセミコロンを含めることをお勧めします。詳細については、「自動セミコロン挿入」で説明します。
セミコロンはステートメントを終了しますが、ブロックは終了しません。ブロックの後にセミコロンが表示されるケースが1つあります。関数式はブロックで終わる式です。 そのような式がステートメントの最後に来る場合は、セミコロンが続きます。
// Pattern: var _ = ___;
var
x
=
3
*
7
;
var
f
=
function
()
{
};
// function expr. inside var decl.
var
foo
;
// declare variable `foo`
識別子は、JavaScriptでさまざまな構文上の役割を果たす名前です。 たとえば、変数の名前は識別子です。識別子は大文字と小文字が区別されます。
大まかに言うと、識別子の最初の文字は、任意のUnicode文字、ドル記号($
)、またはアンダースコア(_
)にすることができます。後続の文字は、さらに任意のUnicode数字にすることができます。したがって、次のものはすべて有効な識別子です。
arg0
_tmp
$elem
π
次の識別子は予約語です。これらは構文の一部であり、変数名(関数名やパラメーター名を含む)として使用することはできません。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
次の3つの識別子は予約語ではありませんが、予約語であるかのように扱う必要があります。
|
|
|
最後に、標準のグローバル変数の名前(第23章を参照)も避ける必要があります。 ローカル変数として使用しても何も壊れませんが、コードは依然として混乱します。
JavaScriptには、プログラミング言語に期待される多くの値があります。ブール値、数値、文字列、配列などです。JavaScriptのすべての値にはプロパティがあります。[1] 各プロパティには、キー(または名前)と値があります。プロパティはレコードのフィールドのように考えることができます。ドット(.
)演算子を使用してプロパティを読み取ります。
value
.
propKey
たとえば、文字列'abc'
にはlength
プロパティがあります。
> var str = 'abc'; > str.length 3
前述の例は、次のように記述することもできます。
> 'abc'.length 3
ドット演算子は、プロパティに値を代入するために使用することもできます。
> var obj = {}; // empty object > obj.foo = 123; // create property `foo`, set it to 123 123 > obj.foo 123
> 'hello'.toUpperCase() 'HELLO'
上記の例では、値'hello'
に対してメソッドtoUpperCase()
を呼び出しました。
JavaScriptでは、値の間でやや恣意的な区別が行われます。
null
、undefined
です。2つの主な違いは、それらがどのように比較されるかです。各オブジェクトには固有の識別子があり、自分自身と(厳密に)等しいだけです。
> var obj1 = {}; // an empty object > var obj2 = {}; // another empty object > obj1 === obj2 false > obj1 === obj1 true
対照的に、同じ値をエンコードするすべてのプリミティブ値は同じと見なされます。
> var prim1 = 123; > var prim2 = 123; > prim1 === prim2 true
次の2つのセクションでは、プリミティブ値とオブジェクトについて詳しく説明します。
以下は、すべてのプリミティブ値(または略してプリミティブ)です。
true
、false
(「ブール値」を参照)1736
、1.351
(「数値」を参照)'abc'
、"abc"
(「文字列」を参照)undefined
、null
(「undefinedとnull」を参照)プリミティブには次の特性があります。
「コンテンツ」が比較される
> 3 === 3 true > 'abc' === 'abc' true
> var str = 'abc'; > str.length = 1; // try to change property `length` > str.length // ⇒ no effect 3 > str.foo = 3; // try to create property `foo` > str.foo // ⇒ no effect, unknown property undefined
(不明なプロパティを読み取ると、常にundefined
が返されます。)
すべての非プリミティブ値はオブジェクトです。最も一般的な種類のオブジェクトは次のとおりです。
プレーンオブジェクト。これらはオブジェクトリテラルで作成できます(「単一のオブジェクト」を参照)。
{
firstName
:
'Jane'
,
lastName
:
'Doe'
}
上記のオブジェクトには、2つのプロパティがあります。プロパティfirstName
の値は'Jane'
で、プロパティlastName
の値は'Doe'
です。
配列。これらは配列リテラルで作成できます(「配列」を参照)。
[
'apple'
,
'banana'
,
'cherry'
]
上記の配列には、数値インデックスを使用してアクセスできる3つの要素があります。たとえば、'apple'
のインデックスは0です。
正規表現。これらは正規表現リテラルで作成できます(「正規表現」を参照)。
/^a+b+$/
オブジェクトには次の特性があります。
識別子が比較される。すべての値には独自の識別子がある
> ({} === {}) // two different empty objects false > var obj1 = {}; > var obj2 = obj1; > obj1 === obj2 true
通常は、プロパティを自由に変更、追加、削除できます(「単一のオブジェクト」を参照)。
> var obj = {}; > obj.foo = 123; // add property `foo` > obj.foo 123
ほとんどのプログラミング言語には、欠落した情報を表す値があります。 JavaScriptには、そのような「非値」が2つあります。それは、undefined
と null
です。
undefined
は「値がない」ことを意味します。初期化されていない変数は undefined
です。
> var foo; > foo undefined
欠落したパラメータは undefined
です。
> function f(x) { return x } > f() undefined
存在しないプロパティを読み取ると、undefined
が返されます。
> var obj = {}; // empty object > obj.foo undefined
null
は「オブジェクトがない」ことを意味します。オブジェクトが期待される場合(パラメータ、オブジェクトの連鎖の最後など)に、非値として使用されます。undefined
と null
にはプロパティがなく、toString()
などの標準メソッドさえありません。
関数は通常、undefined
または null
のいずれかを介して欠落した値を示すことができます。 明示的なチェックによって同じことができます。
if
(
x
===
undefined
||
x
===
null
)
{
...
}
また、undefined
と null
の両方が false
と見なされるという事実を利用することもできます。
if
(
!
x
)
{
...
}
false
、0
、NaN
、および ''
も false
と見なされます(「Truthy and Falsy」を参照)。
値を分類するための演算子が2つあります。typeof
は主にプリミティブ値に使用され、instanceof
はオブジェクトに使用されます。
typeof
は次のようになります。
typeof
value
value
の「型」を記述する文字列を返します。以下に例をいくつか示します。
> typeof true 'boolean' > typeof 'abc' 'string' > typeof {} // empty object literal 'object' > typeof [] // empty array literal 'object'
次の表は、typeof
のすべての結果を示しています。
オペランド | 結果 |
|
|
|
|
真偽値 |
|
数値 |
|
文字列値 |
|
関数 |
|
その他すべての通常の値 |
|
(エンジンが作成した値) | JavaScriptエンジンは、 |
typeof null
が 'object'
を返すのはバグであり、既存のコードを破壊するため、修正できません。これは、null
がオブジェクトであることを意味しません。
instanceof
は次のようになります。
value
instanceof
Constr
value
がコンストラクタ Constr
によって作成されたオブジェクトの場合、true
を返します(「コンストラクタ:オブジェクトのファクトリ」を参照)。以下に例をいくつか示します。
> var b = new Bar(); // object created by constructor Bar > b instanceof Bar true > {} instanceof Object true > [] instanceof Array true > [] instanceof Object // Array is a subconstructor of Object true > undefined instanceof Object false > null instanceof Object false
プリミティブな真偽値型 は、true
と false
の値で構成されます。次の演算子は真偽値を生成します。
&&
(And)、||
(Or)!
(Not)比較演算子
===
、!==
、==
、!=
>
、>=
、<
、<=
JavaScriptが真偽値を期待する場合(例えば、if
ステートメントの条件)、任意の値を使用できます。それはtrue
またはfalse
として解釈されます。次の値はfalse
として解釈されます。
undefined
、null
false
0
、NaN
''
その他すべての値(すべてのオブジェクトを含む!)は、true
と見なされます。false
として解釈される値はfalsyと呼ばれ、true
として解釈される値はtruthyと呼ばれます。関数として呼び出されるBoolean()
は、そのパラメータをブール値に変換します。それを使用して、値がどのように解釈されるかをテストできます。
> Boolean(undefined) false > Boolean(0) false > Boolean(3) true > Boolean({}) // empty object true > Boolean([]) // empty array true
JavaScriptの2項論理演算子はショートサーキットです。つまり、最初のオペランドが結果を決定するのに十分な場合、2番目のオペランドは評価されません。 たとえば、次の式では、関数 foo()
は決して呼び出されません。
false
&&
foo
()
true
||
foo
()
さらに、2項論理演算子は、オペランドのいずれかを返します。これは真偽値である場合もそうでない場合もあります。truthyかどうかのチェックは、どちらを返すかを判断するために使用されます。
&&
)最初のオペランドがfalsyの場合、それを返します。それ以外の場合は、2番目のオペランドを返します。
> NaN && 'abc' NaN > 123 && 'abc' 'abc'
||
)最初のオペランドがtruthyの場合、それを返します。それ以外の場合は、2番目のオペランドを返します。
> 'abc' || 123 'abc' > '' || 123 123
JavaScriptには、2種類の等価性があります。
==
および !=
===
および !==
通常の等価性は、多くの値を等しいと見なします(詳細は、「通常の(寛容な)等価(==、!=)」で説明します)。バグを隠す可能性があるため、常に厳密な等価性を使用することをお勧めします。
JavaScriptのすべての数値は浮動小数点数です。
> 1 === 1.0 true
特殊な数値には、次のものがあります。
NaN
(「数値ではない」)エラー値
> Number('xyz') // 'xyz' can’t be converted to a number NaN
Infinity
ほとんどもエラー値です。
> 3 / 0 Infinity > Math.pow(2, 1024) // number too large Infinity
Infinity
は、他のどの数値よりも大きい(NaN
を除く)。同様に、-Infinity
は他のどの数値よりも小さい(NaN
を除く)。これにより、これらの数値はデフォルト値として役立ちます(例えば、最小値または最大値を検索する場合)。
JavaScriptには、次の算術演算子があります(「算術演算子」を参照)。
number1 + number2
number1 - number2
number1 * number2
number1 / number2
number1 % number2
++variable
、variable++
--variable
、variable--
-value
+value
グローバルオブジェクトMath
(「Math」を参照)は、関数を介して、より多くの算術演算を提供します。
JavaScriptには、ビット単位の演算用の演算子もあります(例えば、ビット単位のAnd。「ビット単位演算子」を参照)。
文字列は、文字列リテラルを介して直接作成できます。 これらのリテラルは、単一引用符または二重引用符で区切られます。バックスラッシュ(\
)は文字をエスケープし、いくつかの制御文字を生成します。以下に例をいくつか示します。
'abc'
"abc"
'Did she say "Hello"?'
"Did she say \"Hello\"?"
'That\'s nice!'
"That's nice!"
'Line 1\nLine 2'
// newline
'Backlash: \\'
単一文字は角括弧でアクセスされます。
> var str = 'abc'; > str[1] 'b'
プロパティ length
は、文字列の文字数をカウントします。
> 'abc'.length 3
すべてのプリミティブと同様に、文字列は不変です。既存の文字列を変更する場合は、新しい文字列を作成する必要があります。
文字列は、プラス(+
)演算子を介して連結されます。これは、オペランドのいずれかが文字列の場合、他のオペランドを文字列に変換します。
> var messageCount = 3; > 'You have ' + messageCount + ' messages' 'You have 3 messages'
複数のステップで文字列を連結するには、+=
演算子を使用します。
> var str = ''; > str += 'Multiple '; > str += 'pieces '; > str += 'are concatenated.'; > str 'Multiple pieces are concatenated.'
文字列には多くの便利なメソッドがあります(「String Prototype Methods」を参照)。以下に例をいくつか示します。
> 'abc'.slice(1) // copy a substring 'bc' > 'abc'.slice(1, 2) 'b' > '\t xyz '.trim() // trim whitespace 'xyz' > 'mjölnir'.toUpperCase() 'MJÖLNIR' > 'abc'.indexOf('b') // find a string 1 > 'abc'.indexOf('x') -1
JavaScriptの条件文とループは、次のセクションで紹介します。
if
ステートメントには、then
句があり、オプションの else
句があり、ブール条件に応じて実行されます。
if
(
myvar
===
0
)
{
// then
}
if
(
myvar
===
0
)
{
// then
}
else
{
// else
}
if
(
myvar
===
0
)
{
// then
}
else
if
(
myvar
===
1
)
{
// else-if
}
else
if
(
myvar
===
2
)
{
// else-if
}
else
{
// else
}
常に中括弧(0個以上のステートメントのブロックを示します)を使用することをお勧めします。 ただし、句が単一のステートメントのみである場合は、そうする必要はありません(制御フロー ステートメント for
および while
にも同じことが当てはまります)。
if
(
x
<
0
)
return
-
x
;
以下はswitch
ステートメントです。fruit
の値は、どのcase
が実行されるかを決定します。
switch
(
fruit
)
{
case
'banana'
:
// ...
break
;
case
'apple'
:
// ...
break
;
default
:
// all other cases
// ...
}
case
の後の「オペランド」には、任意の式を指定できます。これは switch
のパラメータと ===
で比較されます。
for
ループは、次の形式を持ちます。
for
(
⟦«
init
»⟧
;
⟦«
condition
»⟧
;
⟦«
post_iteration
»⟧
)
«
statement
»
init
はループの最初に実行されます。condition
は各ループの反復前にチェックされ、false
になるとループは終了します。post_iteration
は各ループの反復後に実行されます。
この例では、配列 arr
のすべての要素をコンソールに出力します。
for
(
var
i
=
0
;
i
<
arr
.
length
;
i
++
)
{
console
.
log
(
arr
[
i
]);
}
while
ループは、条件が真である間、その本体をループし続けます。
// Same as for loop above:
var
i
=
0
;
while
(
i
<
arr
.
length
)
{
console
.
log
(
arr
[
i
]);
i
++
;
}
do-while
ループは、条件が真である間、その本体をループし続けます。条件は本体の後にあるため、本体は常に少なくとも 1 回は実行されます。
do
{
// ...
}
while
(
condition
);
すべてのループにおいて:
break
はループを終了します。continue
は新しいループの反復を開始します。関数を定義する 1 つの方法は、関数宣言 を使用することです。
function
add
(
param1
,
param2
)
{
return
param1
+
param2
;
}
上記のコードは、2 つのパラメータ param1
と param2
を持ち、両方のパラメータの合計を返す関数 add
を定義します。これがその関数の呼び出し方です。
> add(6, 1) 7 > add('a', 'b') 'ab'
add()
を定義する別の方法は、関数式 を変数 add
に割り当てることです。
var
add
=
function
(
param1
,
param2
)
{
return
param1
+
param2
;
};
関数式は値を生成するため、関数を他の関数の引数として直接渡すために使用できます。
someOtherFunction
(
function
(
p1
,
p2
)
{
...
});
関数宣言は巻き上げ られます。つまり、現在のスコープの先頭に全体が移動されます。これにより、後で宣言された関数を参照できます。
function
foo
()
{
bar
();
// OK, bar is hoisted
function
bar
()
{
...
}
}
var
宣言も巻き上げられますが(「変数は巻き上げられる」を参照)、それらによる割り当ては巻き上げられないことに注意してください。
function
foo
()
{
bar
();
// Not OK, bar is still undefined
var
bar
=
function
()
{
// ...
};
}
JavaScript では、任意の数の引数で任意の関数を呼び出すことができます。言語は決して文句を言いません。ただし、特殊変数 arguments
を介してすべてのパラメータを利用できるようにします。arguments
は配列のように見えますが、配列メソッドは一切持っていません。
> function f() { return arguments } > var args = f('a', 'b', 'c'); > args.length 3 > args[0] // read element at index 0 'a'
JavaScript でパラメータが多すぎる場合、または少なすぎる場合にどのように処理されるかを探るために、次の関数を使用してみましょう(関数 toArray()
は「arguments を配列に変換する」に示されています)。
function
f
(
x
,
y
)
{
console
.
log
(
x
,
y
);
return
toArray
(
arguments
);
}
追加のパラメータは(arguments
を除いて)無視されます。
> f('a', 'b', 'c') a b [ 'a', 'b', 'c' ]
不足しているパラメータは値 undefined
を取得します。
> f('a') a undefined [ 'a' ] > f() undefined undefined []
以下は、パラメータにデフォルト値を割り当てるための一般的なパターンです。
function
pair
(
x
,
y
)
{
x
=
x
||
0
;
// (1)
y
=
y
||
0
;
return
[
x
,
y
];
}
(1)行目では、||
演算子は、x
が truthy (null
、undefined
などではない) であれば x
を返します。それ以外の場合は、2 番目のオペランドを返します。
> pair() [ 0, 0 ] > pair(3) [ 3, 0 ] > pair(3, 5) [ 3, 5 ]
もし引数の数(特定の数のパラメータ)を強制したい場合は、arguments.length
を確認できます。
function
pair
(
x
,
y
)
{
if
(
arguments
.
length
!==
2
)
{
throw
new
Error
(
'Need exactly 2 arguments'
);
}
...
}
arguments
は配列ではなく、単なる 配列のような オブジェクトです(「配列のようなオブジェクトとジェネリックメソッド」を参照)。length
プロパティを持ち、角括弧でインデックスを介してその要素にアクセスできます。ただし、要素を削除したり、配列メソッドを呼び出したりすることはできません。したがって、arguments
を配列に変換する必要がある場合があります。以下の関数がそれを行います(「配列のようなオブジェクトとジェネリックメソッド」で説明されています)。
function
toArray
(
arrayLikeObject
)
{
return
Array
.
prototype
.
slice
.
call
(
arrayLikeObject
);
}
例外を処理する最も一般的な方法は(「第 14 章」を参照)次のとおりです。
function
getPerson
(
id
)
{
if
(
id
<
0
)
{
throw
new
Error
(
'ID must not be negative: '
+
id
);
}
return
{
id
:
id
};
// normally: retrieved from database
}
function
getPersons
(
ids
)
{
var
result
=
[];
ids
.
forEach
(
function
(
id
)
{
try
{
var
person
=
getPerson
(
id
);
result
.
push
(
person
);
}
catch
(
exception
)
{
console
.
log
(
exception
);
}
});
return
result
;
}
try
句は重要なコードを囲み、catch
句は try
句の中で例外がスローされた場合に実行されます。上記のコードを使用すると:
> getPersons([2, -5, 137]) [Error: ID must not be negative: -5] [ { id: 2 }, { id: 137 } ]
厳格モード(「厳格モード」を参照)では、より多くの警告が有効になり、JavaScript がよりクリーンな言語になります(非厳格モードは「ずさんモード」と呼ばれることがあります)。それを有効にするには、JavaScript ファイルまたは <script>
タグの最初に次の行を入力します。
'use strict'
;
関数ごとに厳格モードを有効にすることもできます。
function
functionInStrictMode
()
{
'use strict'
;
}
JavaScript では、変数を使用する前に var
で宣言します。
> var x; > x undefined > y ReferenceError: y is not defined
1 つの var
ステートメントで複数の変数を宣言および初期化できます。
var
x
=
1
,
y
=
2
,
z
=
3
;
ただし、変数ごとに 1 つのステートメントを使用することをお勧めします(理由は「構文」で説明されています)。したがって、上記のステートメントを次のように書き直します。
var
x
=
1
;
var
y
=
2
;
var
z
=
3
;
巻き上げのため(「変数は巻き上げられる」を参照)、通常は関数の最初に変数を宣言するのが最善です。
変数のスコープは常に、現在のブロックとは対照的に、完全な関数です。例:
function
foo
()
{
var
x
=
-
512
;
if
(
x
<
0
)
{
// (1)
var
tmp
=
-
x
;
...
}
console
.
log
(
tmp
);
// 512
}
変数 tmp
が (1) 行目から始まるブロックに限定されず、関数の最後まで存在することがわかります。
各変数宣言は巻き上げ られます。つまり、宣言は関数の先頭に移動されますが、そこで行われる割り当てはそのまま残ります。例として、次の関数の (1) 行目の変数宣言について考えてみましょう。
function
foo
()
{
console
.
log
(
tmp
);
// undefined
if
(
false
)
{
var
tmp
=
3
;
// (1)
}
}
内部的には、上記の関数は次のように実行されます。
function
foo
()
{
var
tmp
;
// hoisted declaration
console
.
log
(
tmp
);
if
(
false
)
{
tmp
=
3
;
// assignment stays put
}
}
各関数は、作成されたスコープから離れた後でも、その周囲の関数の変数への接続を維持します。例:
function
createIncrementor
(
start
)
{
return
function
()
{
// (1)
start
++
;
return
start
;
}
}
(1)行目から始まる関数は、作成されたコンテキストから離れますが、start
のライブバージョンへの接続を維持します。
> var inc = createIncrementor(5); > inc() 6 > inc() 7 > inc() 8
クロージャ は、関数とその周囲のスコープの変数への接続の組み合わせです。したがって、createIncrementor()
が返すものはクロージャです。
グローバルにならないように変数を防ぐなど、新しい変数スコープを導入したい場合があります。JavaScript では、ブロックを使用してこれを行うことはできません。関数を使用する必要があります。ただし、ブロックのような方法で関数を使用するためのパターンがあります。これは IIFE (即時実行関数式、発音は「イフィー」) と呼ばれます。
(
function
()
{
// open IIFE
var
tmp
=
...;
// not a global variable
}());
// close IIFE
上記の例は、コメントを除いて、正確に示されているとおりに入力してください。IIFE は、定義した直後に呼び出される関数式です。関数内には、新しいスコープが存在し、tmp
がグローバルになるのを防ぎます。IIFE の詳細については、「IIFE を介した新しいスコープの導入」を参照してください。
クロージャは、外側の変数への接続を維持しますが、これは場合によっては望ましくないものです。
var
result
=
[];
for
(
var
i
=
0
;
i
<
5
;
i
++
)
{
result
.
push
(
function
()
{
return
i
});
// (1)
}
console
.
log
(
result
[
1
]());
// 5 (not 1)
console
.
log
(
result
[
3
]());
// 5 (not 3)
(1) 行目で返される値は常に i
の現在の値であり、関数が作成されたときの値ではありません。ループが完了すると、i
は値 5 を持ち、これが配列内のすべての関数がその値を返す理由です。(1) 行目の関数に i
の現在の値のスナップショットを受け取らせたい場合は、IIFE を使用できます。
for
(
var
i
=
0
;
i
<
5
;
i
++
)
{
(
function
()
{
var
i2
=
i
;
// copy current i
result
.
push
(
function
()
{
return
i2
});
}());
}
このセクションでは、JavaScript の 2 つの基本的なオブジェクト指向メカニズムである、単一のオブジェクトと、(他の言語のクラスに似たオブジェクトのファクトリである)コンストラクタ について説明します。
すべての値と同様に、オブジェクトにはプロパティがあります。実際、オブジェクトをプロパティの集合と考えることができます。各プロパティは (キー、値) のペアです。キーは文字列であり、値は任意の JavaScript 値です。
JavaScript では、オブジェクトリテラル を使用してプレーンオブジェクトを直接作成できます。
'use strict'
;
var
jane
=
{
name
:
'Jane'
,
describe
:
function
()
{
return
'Person named '
+
this
.
name
;
}
};
上記のオブジェクトには、プロパティ name
と describe
があります。プロパティの読み取り(「取得」)と書き込み(「設定」)ができます。
> jane.name // get 'Jane' > jane.name = 'John'; // set > jane.newProperty = 'abc'; // property created automatically
describe
のような関数値のプロパティは、メソッド と呼ばれます。メソッドは、それらを呼び出すために使用されたオブジェクトを参照するために this
を使用します。
> jane.describe() // call method 'Person named John' > jane.name = 'Jane'; > jane.describe() 'Person named Jane'
in
演算子は、プロパティが存在するかどうかを確認します。
> 'newProperty' in jane true > 'foo' in jane false
存在しないプロパティを読み取ると、値 undefined
が得られます。したがって、前の 2 つのチェックは、次のようにも実行できます。[2]
> jane.newProperty !== undefined true > jane.foo !== undefined false
delete
演算子はプロパティを削除します。
> delete jane.newProperty true > 'newProperty' in jane false
プロパティキーには任意の文字列を指定できます。これまで、オブジェクトリテラルとドット演算子の後のプロパティキーを見てきました。ただし、それらが識別子である場合にのみ、そのように使用できます(「識別子と変数名」を参照)。他の文字列をキーとして使用する場合は、オブジェクトリテラルでそれらを引用符で囲み、角括弧を使用してプロパティを取得および設定する必要があります。
> var obj = { 'not an identifier': 123 }; > obj['not an identifier'] 123 > obj['not an identifier'] = 456;
角括弧を使用すると、プロパティのキーを計算することもできます。
> var obj = { hello: 'world' }; > var x = 'hello'; > obj[x] 'world' > obj['hel'+'lo'] 'world'
メソッドを抽出すると、オブジェクトとの接続が失われます。関数自体はもうメソッドではなく、this
の値は (厳格モードでは) undefined
になります。
例として、前のオブジェクト jane
に戻りましょう。
'use strict'
;
var
jane
=
{
name
:
'Jane'
,
describe
:
function
()
{
return
'Person named '
+
this
.
name
;
}
};
jane
からメソッド describe
を抽出し、それを変数 func
に入れて呼び出したいとします。ただし、これはうまくいきません。
> var func = jane.describe; > func() TypeError: Cannot read property 'name' of undefined
解決策は、すべての関数が持つメソッド bind()
を使用することです。これは、this
が常に指定された値を持つ新しい関数を作成します。
> var func2 = jane.describe.bind(jane); > func2() 'Person named Jane'
すべての関数は、独自の特別な変数 this
を持っています。メソッド内に関数をネストすると、関数の内部からメソッドの this
にアクセスできなくなるため、これは不便です。以下は、配列を反復処理するために関数で forEach
を呼び出す例です。
var
jane
=
{
name
:
'Jane'
,
friends
:
[
'Tarzan'
,
'Cheeta'
],
logHiToFriends
:
function
()
{
'use strict'
;
this
.
friends
.
forEach
(
function
(
friend
)
{
// `this` is undefined here
console
.
log
(
this
.
name
+
' says hi to '
+
friend
);
});
}
}
logHiToFriends
を呼び出すとエラーが発生します。
> jane.logHiToFriends() TypeError: Cannot read property 'name' of undefined
これを修正する2つの方法を見てみましょう。まず、this
を別の変数に保存できます。
logHiToFriends
:
function
()
{
'use strict'
;
var
that
=
this
;
this
.
friends
.
forEach
(
function
(
friend
)
{
console
.
log
(
that
.
name
+
' says hi to '
+
friend
);
});
}
または、forEach
には this
の値を指定できる2番目のパラメータがあります。
logHiToFriends
:
function
()
{
'use strict'
;
this
.
friends
.
forEach
(
function
(
friend
)
{
console
.
log
(
this
.
name
+
' says hi to '
+
friend
);
},
this
);
}
関数式は、JavaScript の関数呼び出しで引数としてよく使用されます。これらの関数式の中から this
を参照する場合は、常に注意してください。
これまで、JavaScript オブジェクトは、他の言語のマップ/辞書リテラルのように見える JavaScript のオブジェクトリテラルが示すように、文字列から値への単なるマップであると考えていたかもしれません。しかし、JavaScript オブジェクトは、真にオブジェクト指向な機能である継承もサポートしています。このセクションでは、JavaScript の継承がどのように機能するかを完全に説明するわけではありませんが、入門するための簡単なパターンを示します。詳細を知りたい場合は、第17章を参照してください。
関数は、「本物の」関数およびメソッドであることに加えて、JavaScript では別の役割を果たします。それは、new
演算子を介して呼び出された場合、コンストラクタ、つまりオブジェクトのファクトリになることです。したがって、コンストラクタは他の言語のクラスに大まかに似たものです。慣例として、コンストラクタの名前は大文字で始まります。例:
// Set up instance data
function
Point
(
x
,
y
)
{
this
.
x
=
x
;
this
.
y
=
y
;
}
// Methods
Point
.
prototype
.
dist
=
function
()
{
return
Math
.
sqrt
(
this
.
x
*
this
.
x
+
this
.
y
*
this
.
y
);
};
コンストラクタには2つの部分があることがわかります。まず、関数 Point
はインスタンスデータを設定します。次に、プロパティ Point.prototype
にはメソッドを持つオブジェクトが含まれます。前者のデータは各インスタンスに固有であり、後者のデータはすべてのインスタンスで共有されます。
Point
を使用するには、new
演算子を介して呼び出します。
> var p = new Point(3, 5); > p.x 3 > p.dist() 5.830951894845301
p
は Point
のインスタンスです。
> p instanceof Point true
配列は、ゼロから始まる整数インデックスを介してアクセスできる要素のシーケンスです。
配列リテラルは、配列を作成するのに便利です。
> var arr = [ 'a', 'b', 'c' ];
前の配列には、文字列 'a'
、'b'
、および 'c'
の3つの要素があります。これらは整数インデックスを介してアクセスできます。
> arr[0] 'a' > arr[0] = 'x'; > arr [ 'x', 'b', 'c' ]
length
プロパティは、配列に含まれる要素の数を示します。これを使用して要素を追加したり、要素を削除したりできます。
> var arr = ['a', 'b']; > arr.length 2 > arr[arr.length] = 'c'; > arr [ 'a', 'b', 'c' ] > arr.length 3 > arr.length = 1; > arr [ 'a' ]
in
演算子は配列にも機能します。
> var arr = [ 'a', 'b', 'c' ]; > 1 in arr // is there an element at index 1? true > 5 in arr // is there an element at index 5? false
配列はオブジェクトであり、したがってオブジェクトプロパティを持つことができることに注意してください。
> var arr = []; > arr.foo = 123; > arr.foo 123
配列には多くのメソッドがあります(配列プロトタイプメソッドを参照)。以下にいくつかの例を示します。
> var arr = [ 'a', 'b', 'c' ]; > arr.slice(1, 2) // copy elements [ 'b' ] > arr.slice(1) [ 'b', 'c' ] > arr.push('x') // append an element 4 > arr [ 'a', 'b', 'c', 'x' ] > arr.pop() // remove last element 'x' > arr [ 'a', 'b', 'c' ] > arr.shift() // remove first element 'a' > arr [ 'b', 'c' ] > arr.unshift('x') // prepend an element 3 > arr [ 'x', 'b', 'c' ] > arr.indexOf('b') // find the index of an element 1 > arr.indexOf('y') -1 > arr.join('-') // all elements in a single string 'x-b-c' > arr.join('') 'xbc' > arr.join() 'x,b,c'
要素を反復処理するための配列メソッドがいくつかあります(反復処理(非破壊的)を参照)。最も重要な2つは forEach
と map
です。
forEach
は配列を反復処理し、現在の要素とそのインデックスを関数に渡します。
[
'a'
,
'b'
,
'c'
].
forEach
(
function
(
elem
,
index
)
{
// (1)
console
.
log
(
index
+
'. '
+
elem
);
});
上記のコードは次の出力を生成します。
0. a 1. b 2. c
(1)行の関数は、引数を自由に無視できることに注意してください。たとえば、パラメータ elem
のみを持つことができます。
map
は、既存の配列の各要素に関数を適用して、新しい配列を作成します。
> [1,2,3].map(function (x) { return x*x }) [ 1, 4, 9 ]
JavaScript には正規表現の組み込みサポートがあります(第19章では、チュートリアルを参照し、それらがどのように機能するかを詳しく説明しています)。それらはスラッシュで区切られます。
/^abc$/
/
[
A
-
Za
-
z0
-
9
]
+
/
> /^a+b+$/.test('aaab') true > /^a+b+$/.test('aaa') false
> /a(b+)a/.exec('_abbba_aba_') [ 'abbba', 'bbb' ]
返された配列には、インデックス0で完全一致、インデックス1で最初のグループのキャプチャなどが含まれます。このメソッドを繰り返し呼び出してすべての一致を取得する方法があります(RegExp.prototype.exec:キャプチャグループで説明)。
> '<a> <bbb>'.replace(/<(.*?)>/g, '[$1]') '[a] [bbb]'
replace
の最初のパラメータは、/g
フラグを持つ正規表現である必要があります。そうでない場合、最初の出現のみが置換されます。置換を計算するために関数を使用する方法もあります(String.prototype.replace:検索と置換で説明)。
Math
(第21章を参照) は、算術関数を持つオブジェクトです。以下にいくつかの例を示します。
> Math.abs(-2) 2 > Math.pow(3, 2) // 3 to the power of 2 9 > Math.max(2, -1, 5) 5 > Math.round(1.9) 2 > Math.PI // pre-defined constant for π 3.141592653589793 > Math.cos(Math.PI) // compute the cosine for 180° -1
JavaScript の標準ライブラリは比較的質素ですが、他にも使用できるものがたくさんあります。