JavaScriptは、プログラミング言語で期待されるほとんどの値を備えています。ブール値、数値、文字列、配列などです。すべての通常のJavaScriptの値にはプロパティがあります。[9] 各プロパティにはキー(または名前)と値があります。プロパティはレコードのフィールドのように考えることができます。ドット演算子(.
)を使用してプロパティにアクセスします。
> var obj = {}; // create an empty object > obj.foo = 123; // write property 123 > obj.foo // read property 123 > 'abc'.toUpperCase() // call method 'ABC'
本章では、JavaScriptの型システムの概要を説明します。
JavaScriptには、ECMAScript言語仕様の第8章に従って、6つの型しかありません。
ECMAScript言語型は、ECMAScript言語を使用してECMAScriptプログラマが直接操作する値に対応します。ECMAScript言語型は次のとおりです。
- Undefined、Null
- Boolean、String、Number、および
- Object
したがって、コンストラクタはインスタンスを持つと言われている場合でも、技術的には新しい型を導入しません。
言語のセマンティクスと型システムのコンテキストでは、「静的」とは通常、「コンパイル時」または「プログラムを実行せずに」を意味し、「動的」とは「実行時」を意味します。
静的に型付けされた言語では、変数、パラメータ、オブジェクトのメンバー(JavaScriptではプロパティと呼びます)には、コンパイラがコンパイル時に認識する型があります。コンパイラはこの情報を使用して型チェックを実行し、コンパイル済みコードを最適化できます。
静的に型付けされた言語でも、変数には動的型(実行時の特定の時点での変数の値の型)もあります。動的型は静的型と異なる場合があります。(Javaの例)
Object
foo
=
"abc"
;
foo
の静的型はObject
です。動的型はString
です。
JavaScriptは動的に型付けされています。変数の型は通常、コンパイル時には認識されません。
型情報がある場合、演算(関数の呼び出し、演算子の適用など)で使用される値が正しい型かどうかを確認できます。静的に型チェックされた言語は、この種のチェックをコンパイル時に実行し、動的に型チェックされた言語は実行時に実行します。言語は静的に型チェックされ、動的に型チェックされることもできます。チェックが失敗すると、通常はエラーまたは例外が発生します。
JavaScriptは非常に限られた種類の動的型チェックを実行します。
> var foo = null; > foo.prop TypeError: Cannot read property 'prop' of null
ただし、ほとんどの場合、何も起こらずに失敗するか、機能します。たとえば、存在しないプロパティにアクセスすると、undefined
という値が返されます。
> var bar = {}; > bar.prop undefined
JavaScriptでは、型が合わない値を処理する主な方法は、型強制することです。型強制とは、暗黙的な型変換を意味します。ほとんどのオペランドは型強制されます。
> '3' * '4' 12
JavaScriptの組み込み型変換メカニズムは、Boolean
、Number
、String
、Object
の型のみをサポートしています。あるコンストラクタのインスタンスを別のコンストラクタのインスタンスに変換する標準的な方法はありません。
厳密に型付けされたと弱く型付けされたという用語には、一般的に意味のある定義がありません。これらの用語は使用されますが、通常は誤って使用されています。静的に型付けされた、静的に型チェックされたなどを使用する方が良いでしょう。
JavaScriptはある程度恣意的な値の区別をします。
null
、undefined
です。両者の主な違いは比較方法です。各オブジェクトは一意のアイデンティティを持ち、それ自体にのみ(厳密に)等しくなります。
> var obj1 = {}; // an empty object > var obj2 = {}; // another empty object > obj1 === obj2 false > var obj3 = obj1; > obj3 === obj1 true
これとは対照的に、同じ値をエンコードするすべてのプリミティブ値は同じと見なされます。
> var prim1 = 123; > var prim2 = 123; > prim1 === prim2 true
次の2つのセクションでは、プリミティブ値とオブジェクトを詳しく説明します。
true
、false
(第10章を参照)1736
、1.351
(第11章を参照)'abc'
、"abc"
(第12章を参照)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
が返されます。)
プリミティブでないすべての値はオブジェクトです。最も一般的なオブジェクトの種類は次のとおりです。
プレーンオブジェクト(コンストラクタObject
)はオブジェクトリテラルで作成できます(第17章を参照)
{
firstName
:
'Jane'
,
lastName
:
'Doe'
}
上記のオブジェクトには2つのプロパティがあります。プロパティfirstName
の値は'Jane'
、プロパティlastName
の値は'Doe'
です。
配列(コンストラクタArray
)は 配列リテラルで作成できます(第18章を参照)
[
'apple'
,
'banana'
,
'cherry'
]
上記の配列には、数値インデックスを使用してアクセスできる3つの要素があります。たとえば、'apple'
のインデックスは0です。
正規表現(コンストラクタRegExp
)は正規表現リテラルで作成できます(第19章を参照)
/^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
は「値なし」を意味します(プリミティブでもオブジェクトでもありません)。初期化されていない変数、不足しているパラメータ、不足しているプロパティには、その非値があります。また、明示的に何も返されていない場合、関数は暗黙的にそれを返します。null
は「オブジェクトなし」を意味します。オブジェクトが期待される場合(パラメータとして、オブジェクトのチェーンのメンバーとしてなど)に非値として使用されます。undefined
とnull
は、プロパティアクセスによって例外が発生する唯一の値です。
> function returnFoo(x) { return x.foo } > returnFoo(true) undefined > returnFoo(0) undefined > returnFoo(null) TypeError: Cannot read property 'foo' of null > returnFoo(undefined) TypeError: Cannot read property 'foo' of undefined
undefined
は、存在しないことを示すメタ値として使用されることもあります。これとは対照的に、null
は空を示します。たとえば、JSONノードビジター(データの変換:ノードビジターによるを参照)は、
undefined
を返し、null
に設定するためにnull
を返します。ここでは、undefined
とnull
が発生するさまざまなシナリオを確認します。
初期化されていない変数はundefined
です。
> var foo; > foo undefined
不足しているパラメータはundefined
です。
> function f(x) { return x } > f() undefined
存在しないプロパティを読み取ると、undefined
が返されます。
> var obj = {}; // empty object > obj.foo undefined
そして、関数は、明示的に何も返されていない場合、暗黙的にundefined
を返します。
> function f() {} > f() undefined > function g() { return; } > g() undefined
null
は、プロトタイプチェーンの最後の要素です(オブジェクトのチェーン。 レイヤー2:オブジェクト間のプロトタイプ関係を参照)
> Object.getPrototypeOf(Object.prototype) null
null
は、文字列に正規表現が一致しなかった場合にRegExp.prototype.exec()
によって返されます。
> /x/.exec('aaa') null
次のセクションでは、undefined
とnull
を個別にチェックする方法、またはいずれかが存在するかどうかをチェックする方法を確認します。
厳密な等価演算子(===
)は、undefined
をチェックするための標準的な方法です。
if
(
x
===
undefined
)
...
typeof
演算子(typeof:プリミティブの分類)を使用してundefined
をチェックすることもできますが、通常は前述の方法を使用する必要があります。
ほとんどの関数では、undefined
またはnull
のいずれかを使用して欠損値を示すことができます。これら両方をチェックする1つの方法は、明示的な比較です。
// Does x have a value?
if
(
x
!==
undefined
&&
x
!==
null
)
{
...
}
// Is x a non-value?
if
(
x
===
undefined
||
x
===
null
)
{
...
}
もう1つの方法は、undefined
とnull
の両方がfalse
とみなされるという事実を利用することです(Truthy and Falsy Valuesを参照)。
// Does x have a value (is it truthy)?
if
(
x
)
{
...
}
// Is x falsy?
if
(
!
x
)
{
...
}
false
、0
、NaN
、および''
もfalse
とみなされます。
単一の非値が、undefined
とnull
の両方の役割を果たすことができます。なぜJavaScriptにはそのような値が2つ存在するのでしょうか?その理由は歴史的なものです。
JavaScriptは、Javaのアプローチを採用し、値をプリミティブとオブジェクトに分割しました。また、Javaの「オブジェクトではない」値であるnull
も使用しました。C(ただしJavaではない)で設定された前例に従って、null
は数値に強制変換されると0になります。
> Number(null) 0 > 5 + null 5
最初のバージョンのJavaScriptには例外処理がありませんでした。そのため、初期化されていない変数や欠損プロパティなどの例外的なケースは、値によって示す必要がありました。null
は良い選択肢だったでしょうが、Brendan Eichは当時2つのことを避けたいと考えていました。
その結果、Eichはundefined
を言語に追加の非値として追加しました。これはNaN
に強制変換されます。
> Number(undefined) NaN > 5 + undefined NaN
undefined
はグローバルオブジェクトのプロパティです(したがってグローバル変数です。The Global Objectを参照)。ECMAScript 3では、undefined
を読み取る際には注意が必要でした。なぜなら、その値を誤って変更することが簡単だったからです。ECMAScript 5では、undefined
は読み取り専用であるため、それは必要ありません。
変更されたundefined
から保護するために、2つのテクニックが人気でした(それらは古いJavaScriptエンジンにも関連しています)。
グローバルundefined
(間違った値を持っている可能性があります)をシャドウします。
(
function
(
undefined
)
{
if
(
x
===
undefined
)
...
// safe now
}());
// don’t hand in a parameter
前のコードでは、undefined
は関数の呼び出しによって値が提供されていないパラメーターであるため、正しい値を持つことが保証されています。
void 0
と比較します。これは常に(正しい)undefined
です(The void Operatorを参照)。
if
(
x
===
void
0
)
// always safe
3つのプリミティブ型boolean、number、およびstringには、対応するコンストラクタBoolean
、Number
、String
があります。それらのインスタンス(いわゆるラッパーオブジェクト)は、プリミティブ値をラップします。コンストラクタは2つの方法で使用できます。
コンストラクタとして、それらはラップするプリミティブ値と大きく互換性のないオブジェクトを作成します。
> typeof new String('abc') 'object' > new String('abc') === 'abc' false
関数として、それらは値を対応するプリミティブ型に変換します(Functions for Converting to Boolean, Number, String, and Objectを参照)。これは推奨される変換方法です。
> String(123) '123'
ラッパーオブジェクトを避けることがベストプラクティスと考えられています。通常、それらは必要ありません。オブジェクトができることでプリミティブができないことは(変異するのを除いて)ありません。(これは、JavaScriptがプリミティブとオブジェクトの違いを継承したJavaとは異なります!)
プリミティブ値(例:'abc'
)は、new String('abc')
などのラッパーインスタンスとは根本的に異なります。
> typeof 'abc' // a primitive value 'string' > typeof new String('abc') // an object 'object' > 'abc' instanceof String // never true for primitives false > 'abc' === new String('abc') false
ラッパーインスタンスはオブジェクトであり、JavaScriptではオブジェクトを比較する方法はありません。寛容な等価演算子==
による比較もできません(Equality Operators: === Versus ==を参照)。
> var a = new String('abc'); > var b = new String('abc'); > a == b false
ラッパーオブジェクトを使用するユースケースが1つあります。プリミティブ値にプロパティを追加したい場合です。プリミティブをラップし、ラッパーオブジェクトにプロパティを追加します。値を操作する前に、それをアンラップする必要があります。
ラッパーコンストラクタを呼び出すことでプリミティブをラップします。
new
Boolean
(
true
)
new
Number
(
123
)
new
String
(
'abc'
)
メソッドvalueOf()
を呼び出すことでプリミティブをアンラップします。すべてのオブジェクトにはこのメソッドがあります(Conversion to Primitiveで説明)。
> new Boolean(true).valueOf() true > new Number(123).valueOf() 123 > new String('abc').valueOf() 'abc'
ラッパーオブジェクトをプリミティブに適切に変換すると、数値と文字列は抽出されますが、ブール値は抽出されません。
> Boolean(new Boolean(false)) // does not unwrap true > Number(new Number(123)) // unwraps 123 > String(new String('abc')) // unwraps 'abc'
その理由は、Converting to Booleanで説明されています。
プリミティブには独自のメソッドがなく、ラッパーから借用します。
>
'abc'
.
charAt
===
String
.
prototype
.
charAt
true
ゆるいモードと厳格モードでは、この借用の処理が異なります。ゆるいモードでは、プリミティブは動的にラッパーに変換されます。
String
.
prototype
.
sloppyMethod
=
function
()
{
console
.
log
(
typeof
this
);
// object
console
.
log
(
this
instanceof
String
);
// true
};
''
.
sloppyMethod
();
// call the above method
厳格モードでは、ラッパーのプロトタイプからのメソッドが透過的に使用されます。
String
.
prototype
.
strictMethod
=
function
()
{
'use strict'
;
console
.
log
(
typeof
this
);
// string
console
.
log
(
this
instanceof
String
);
// false
};
''
.
strictMethod
();
// call the above method
型強制とは、ある型の値を別の型の値に暗黙的に変換することです。JavaScriptのほとんどの演算子、関数、メソッドは、オペランドと引数を、必要とする型に強制変換します。たとえば、乗算演算子(*
)のオペランドは数値に強制変換されます。
> '3' * '4' 12
別の例として、オペランドの1つが文字列の場合、プラス演算子(+
)はもう一方を文字列に変換します。
> 3 + ' times' '3 times'
そのため、JavaScriptは値の型が間違っていることをめったに文句を言いません。たとえば、プログラムは通常、ユーザーが入力した数値であっても、オンラインフォームやGUIウィジェットからユーザー入力を文字列として受け取ります。数値としての文字列を数値として扱うと、警告は表示されず、予期しない結果になります。たとえば
var
formData
=
{
width
:
'100'
};
// You think formData.width is a number
// and get unexpected results
var
w
=
formData
.
width
;
var
outer
=
w
+
20
;
// You expect outer to be 120, but it’s not
console
.
log
(
outer
===
120
);
// false
console
.
log
(
outer
===
'10020'
);
// true
このような場合、早い段階で適切な型に変換する必要があります。
var
w
=
Number
(
formData
.
width
);
次の関数は、値をブール値、数値、文字列、またはオブジェクトに変換するための推奨される方法です。
Boolean()
(Converting to Booleanを参照)値をブール値に変換します。次の値はfalse
に変換されます。これらはいわゆる「falsy」値です。
undefined
、null
false
0
、NaN
''
他のすべての値は「truthy」とみなされ、true
に変換されます(すべてのオブジェクトを含む!)。
Number()
(Converting to Numberを参照)undefined
はNaN
になります。null
は0
になります。false
は0
になり、true
は1
になります。String()
(Converting to Stringを参照)値を文字列に変換します。すべてのプリミティブに対して明らかな結果になります。たとえば、
> String(null) 'null' > String(123.45) '123.45' > String(false) 'false'
Object()
(Converting Any Value to an Objectを参照)オブジェクトをそれ自身に、undefined
とnull
を空のオブジェクトに、プリミティブをラップされたプリミティブに変換します。たとえば、
> var obj = { foo: 123 }; > Object(obj) === obj true > Object(undefined) {} > Object('abc') instanceof String true
Boolean()
、Number()
、String()
、およびObject()
は関数として呼び出されます。通常、それらをコンストラクタとして使用することはありません。そうすると、それ自身のインスタンスを作成します(Wrapper Objects for Primitivesを参照)。
値を数値または文字列に変換するには、最初に任意のプリミティブ値に変換してから、最終的な型に変換します(Functions for Converting to Boolean, Number, String, and Objectで説明)。
ECMAScript仕様には、内部関数ToPrimitive()
(JavaScriptからはアクセスできません)があり、この変換を実行します。ToPrimitive()
を理解することで、オブジェクトが数値と文字列に変換される方法を構成できます。次のシグネチャがあります。
ToPrimitive
(
input
,
PreferredType
?
)
オプションのパラメーターPreferredType
は、変換の最終的な型を示します。これは、ToPrimitive()
の結果が数値に変換されるか文字列に変換されるかによって、Number
またはString
のいずれかになります。
PreferredType
がNumber
の場合、次の手順を実行します。
input
がプリミティブの場合、それを返します(これ以上やることはありません)。input
はオブジェクトです。input.valueOf()
を呼び出します。結果がプリミティブの場合、それを返します。input.toString()
を呼び出します。結果がプリミティブの場合、それを返します。TypeError
をスローします(input
をプリミティブに変換できないことを示します)。PreferredType
がString
の場合、手順2と3が入れ替えられます。PreferredType
は省略することもできます。日付の場合はString
、それ以外の値の場合はNumber
とみなされます。これは、演算子+
と==
がToPrimitive()
を呼び出す方法です。
valueOf()
のデフォルトの実装はthis
を返し、toString()
のデフォルトの実装は型情報を返します。
> var empty = {}; > empty.valueOf() === empty true > empty.toString() '[object Object]'
したがって、Number()
はvalueOf()
をスキップし、toString()
の結果を数値に変換します。つまり、'[object Object]'
をNaN
に変換します。
> Number({}) NaN
次のオブジェクトはvalueOf()
をカスタマイズしており、Number()
に影響を与えますが、String()
には何も変更しません。
> var n = { valueOf: function () { return 123 } }; > Number(n) 123 > String(n) '[object Object]'
次のオブジェクトはtoString()
をカスタマイズしています。その結果を数値に変換できるため、Number()
は数値を返すことができます。
> var s = { toString: function () { return '7'; } }; > String(s) '7' > Number(s) 7