(2020.4) ページを分割、参照する仕様を更新。
JavaScript は prototype-based なオブジェクト指向プログラミング言語で、メジャなプログラミング言語で通常思い描くようなクラスはない。Javaなどメジャな言語はたいてい、class-based のオブジェクト指向言語。
JavaScript にも「型」(type) はある。しかし、JavaScript の型システムは相当に壊れている。大きく、1) プリミティブ値, 2) オブジェクト, 3) 関数がある.
null と undefined を区別するのが珍しい。でも、単に面倒になっているだけのような。
undefined値
undefined ではなくエラーになる。
関数から戻り値なしで抜けたときの値は undefined. 最後に実行した値ではない.
null値
undefined とは区別される。つまり, null === undefined は偽になる。
Boolean 型は true と false のみ。真偽値判定は, Boolean 型を含むあらゆるオブジェクトに対して行える。エラーにはならない。
1 / 3 は 0.3333333333333333 になる。
String 型のみ。文字列は immutable object. よくある記事で replace() で置換、とあるが、破壊的に変更するのではない。
文字列リテラルは,ダブルクォーテーションかシングルクォーテーションで囲む。どちらで囲んでも扱いに違いはない。
文字列のなかで, \の直後に改行で、文字列を継続する。
Template Literal (ES2015; ES6 で導入) を使えば, 文字列中に式を埋め込むことができる。
エスケープシーケンスは次のいずれか;
| エスケープシーケンス | 説明 |
|---|---|
\' | ' |
\" | " |
\\ | \ 自身 |
| \b | バックスペース <BS> |
| \f | フォームフィード <FF> |
| \n | 改行文字 (LF) \u000a |
| \r | 復帰文字 (CR) \u000d |
| \t | 文字タブ文字 (HT) \u0009 |
| \v | LINE TABULATION <VT> |
| \xhh | 16進数表記。hは2桁固定で,0〜9,a〜f,A〜F |
| \udddd | UTF-16 の Unicode表記。dは4桁固定で,0〜9,a〜f,A〜F. surrogate pair は, UCS-4 にせずに, 分けて表記する。えー? |
Numberとは型が異なるので, 1n === 1 が偽になる。
JavaScript では,「関数」もオブジェクトであって、プロパティを持てるのが意外です。例えば name プロパティで、関数の名前を得ることができます。
typeof 演算子typeof 演算子で、値の型を得ることができます。しかし, null については壊れています。また、オブジェクトは, 関数以外はすべて "object" になってしまいます。
| 値または型 | typeof obj | 備考 |
|---|---|---|
undefined値 | "undefined" | |
null値 | "object" | "null" ではない! 変更が提案されたこともあったが、互換性維持のため。 |
Boolean型 | "boolean" | |
Number型 | "number" | |
String型 | "string" | |
Symbol型 | "symbol" | ES2015 (ES6) で導入. |
callメソッドを持つオブジェクト
| "function" | |
| その他のオブジェクト | "object" |
null と undefined 以外のプリミティブ値について、メソッド呼び出しの形でその型のメソッドを呼び出すことができます。内部で自動的にオブジェクトに変換される (auto-boxing) ためです。
ただ、boxing されたオブジェクトに対して typeof は, すべて 'object' を返してしまう。混乱の元なので、auto-boxing 以外に自分で次のような式は書くべきでない;
Object.prototype.toStringObject.prototype.toString.call() で, "[object クラス名]" が返る。引数としてプリミティブ値やリテラルを与えてもよい (auto-boxingされる).
字面の "prototype" も "call" も間違いではない。call() の引数をレシーバとして Object.prototype.toString() を呼び出す。
null は "[object Null]" になって、混乱している。また、自作のクラスは, 後述の設定をしないとすべて "[object Object]" になってしまう。基本的には組込みクラス向け。あまり簡単ではない。
具体的な挙動は、次のようになっている. 厳密には「型」で判定しているわけではないが、だいたい型のとおりになる。
| 条件 | Object.prototype.toString 値
| |
|---|---|---|
undefined | "Undefined" | |
null | "Null" | |
IsArray(obj) が真 | "Array" | |
| 文字列 | "String". | |
| [[ParameterMap]]内部スロットを持つ | "Arguments" | |
| [[Call]]内部スロットを持つ (呼出し可能) | "Function" | |
| [[ErrorData]]内部スロットを持つ | "Error" | |
| [[BooleanData]]内部スロットを持つ | "Boolean" | |
| [[NumberData]]内部スロットを持つ | "Number" | NaN, Infinity もこれになる。 |
| [[DateValue]]内部スロットを持つ | "Date" | |
| [[RegExpMatcher]]内部スロットを持つ | "RegExp" | |
@@toStringTag 値が文字列 | @@toStringTag 値
| |
| それ以外 | "Object" |
@@toStringTag Symbol.toStringTag プロパティに文字列を設定すれば、それが得られる。しかしこれ、自作クラスでは, 自動的には設定されない。
設定ずみの組込みクラス (ES2015/ES6):
"Array Iterator"
"ArrayBuffer"
"DataView"
"Generator"
"GeneratorFunction"
"JSON"
"Map"
"Map Iterator"
"Math"
"Module"
"Promise"
"Set"
"Set Iterator"
"String Iterator"
"Symbol"
"WeakMap"
"WeakSet"
ES2017 (ES8) で, 次が追加になっている:
SharedArrayBuffer, Atomics, AsyncFunction
自作クラスで設定するには、次のようにする。
constructor.name現代は、もっと簡単に, オブジェクト.constructor.name でクラス名が得られる。
null, undefined 以外について、プリミティブ値でも大丈夫。
巷の解説は、意外と不正確なものが多い。クラス (関数オブジェクト) と prototypeプロパティは別物、ということが意識できていない。
「オブジェクト instanceof クラス」(真偽値) で、オブジェクトがクラスまたはそのサブクラスのインスタンスか、が分かる。左辺をプリミティブ値にすると, いつでも false になる。(auto-boxing されない.)
実際の動作は, オブジェクト の [[Prototype]] 内部プロパティ(の連なり)と、クラス (関数オブジェクト) の prototype プロパティとが同一オブジェクトの場合, true になる。
右辺は, callable でなければならない。そうでなければ TypeError になる。
似た方法で,「クラス.prototype.isPrototypeOf(オブジェクト)」で、オブジェクトがクラスまたはそのサブクラスのインスタンスの場合に true が返る。プリミティブ値を与えると, 同様に, きまってfalseになる。
オブジェクトの [[Prototype]] 内部プロパティとの一致を確認するのも同じ。
これら二つは、通常は同じだが, クラスを使わず直接プロトタイプからオブジェクトを生成するようなケースで、挙動が異なる。
=== クラス次のようにすれば、もっと簡単に判定できる。上とことなり, スーパークラスとの比較は false になる。これを利用すると, switch文でクラスでの分岐もできる。