「必要なのはJavaScript言語仕様書とブラウザーコンソールだけ!」
シリーズ、その5。
#Object.create で最も単純なオブジェクトを作る
Object.create 関数の仕様によると、
引数として渡されたプロトタイプを持つオブジェクトを生成するとあり、Nullも渡せると書いてある。
できるだけ単純なオブジェクトから作ってみることにする。
##Nullを渡してオブジェクトを作ってみる。
var o1 = Object.create(null)
o1
問題なく作れた。これはいったいどういうオブジェクトなんだろう。
##オブジェクトのプロトタイプを調べる
では、Object.getPrototypeOf() 関数を使って、o1のプロトタイプを調べてみる。
予想はNullが返される。
Object.getPrototypeOf(o1)
結果は予想どおり。
さて、このプロトタイプが無い(Nullな)オブジェクトだけど、一体役に立つのだろうか?
#プロトタイプを持たないオブジェクトの性質
prototype の利用目的は継承とプロパティの共有なので、逆に言うとprototypeを持たないオブジェクトはそのどちらも利用出来ないということになる。
コンソールに
var o1 = Object.create(null)
o1.toString()
っと打ち込むと
このように怒られてしまう。toString()
なんてありませんよ、と言っている。
なぜこうなるかというと、o1は「プロパティの共有」で toString をどこかから探してくる、ということが出来ないからである。
o1はこの状態だと誰からも何も継承しないし共有もしないので、全くもって役に立たない、天涯孤独な寂しいオブジェクトを作ってしまったわけだ・・・
#プロトタイプを持たないオブジェクトにせめて自分だけのプロパティを!
誰ともプロパティを共有していないからといって、自分専用のプロパティを持ってはいけない、ということにはならない。
Object.defineProperty() 関数を使って新しくプロパティを加えてあげよう。
var o1 = Object.create(null)
Object.defineProperty(o1, "id", {value : 123});
o1.id
idという独自のプロパティを持たせることが出来た。
こうやってちまちまとオブジェクトをほぼゼロから定義していくやり方もある。可能だしJavaScriptの理解の助けにはなるが、実際的には役に立ちそうにない。
#Null以外で単純なオブジェクトを作る
プロトタイプがNullだとつまらないので、せめてObjectオブジェクトのプロトタイプを指定して新規オブジェクトを作ってみる。
var o2 = Object.create(Object.prototype)
o2.toString()
今度はtoStringという名前のプロパティが見つかった。呼び出しが成功し、オブジェクトをStringで表す値が返されている。さて、このtoString関数は一体どこから継承してきたのかというと、それはo2のプロトタイプである Object.prototype からである。
ちょっとややこしくなって来たけど、コンソールで確認してみる。
Object.getPrototypeOf(o2) == Object.prototype
結果:
o2のプロトタイプはObject.prototypeと同じだと出た。
当然といえば当然。何しろ
var o2 = Object.create(Object.prototype)
と指定したわけだから。
要点
Object.create
にprototypeを渡すと、それをプロトタイプとするオブジェクトを生成してくれる。
#誰がそのプロパティを持っているか?
継承とか共有が起こると、そもそも誰のモノなのか、といったあたりで混乱が起こりやすい。とくにバグで予想外の結果が出る場合は、想定されていないオブジェクトのプロパティが使われていたりする可能性がある。
Object.getOwnPropertyNames(O) を使って、オブジェクトが独自に定義しているプロパティをリストアップすることが出来る(リストが空の場合もありうる)
以下、コンソールでo2と、そのプロトタイプであるObject.prototypeそれぞれのプロパティをそれぞれリストアップさせてみる。
Object.getOwnPropertyNames(o2)
Object.getOwnPropertyNames(Object.prototype)
結果:
o2は何も独自に定義していないので、返された値は要素ゼロの列。
対して、Object.prototype の方はいくつものプロパティを定義しているのが分かる。
そして toString
はObject.prototypeしか定義していなから、o2.toString()
はそれを継承することになる。
要点
オブジェクトは、そのオブジェクトのprototypeのプロパティを継承する。
ということはJavaScriptプログラミングではprototypeのプロパティにどんなものがあるのかを知ることが重要になってくる。Object.prototypeも含めて built-inオブジェクトのprototypeをより理解することが大切だということだ。
#built-inオブジェクトのprototypeを調べる
ここではいくつかの built-in オブジェクトのprototypeをObject.getOwnPropertyNames
を使って見てみる。
JavaScript関連の本やリファレンスを見ても分かることだが、コンソールと対話式で調べた方が便利な場合も多いかもしれない。
調べるプロトタイプは:
- Object.prototype
- Function.prototype
- String.prototype
- Array.prototype
Object.prototype
> Object.getOwnPropertyNames(Object.prototype)
< ["constructor", "toString", "toLocaleString", "valueOf", "hasOwnProperty", "isPrototypeOf", "propertyIsEnumerable", "__defineGetter__", "__lookupGetter__", "__defineSetter__", "__lookupSetter__", "__proto__"]
最もよく使うtoString
メソッドはここで定義されている。
hasOwnProperty
やisPrototypeOf
などは動的に型を扱えるJavaScriptの特徴的なメソッドだと言える。
Function.prototype
Object.getOwnPropertyNames(Function.prototype)
["length", "name", "arguments", "caller", "constructor", "bind", "toString", "call", "apply"]
もっとも関数らしいメソッドがcall
。他にもname
やarguments
、そしてcaller
なんてものもある。
この中で特に興味深いのはbind
メソッド。これは後述するが、オブジェクトのインスタンスとFunctionオブジェクトと深い係わりがある。
String.prototype
Object.getOwnPropertyNames(String.prototype)
["length", "constructor", "valueOf", "toString", "charAt", "charCodeAt", "codePointAt", "concat", "endsWith", "includes", "indexOf", "lastIndexOf", "localeCompare", "match", "normalize", "repeat", "replace", "search", "slice", "split", "substring", "substr", "startsWith", "toLowerCase", "toLocaleLowerCase", "toUpperCase", "toLocaleUpperCase", "trim", "trimLeft", "trimRight", "link", "anchor", "fontcolor", "fontsize", "big", "blink", "bold", "fixed", "italics", "small", "strike", "sub", "sup"]
文字数を返すlength
はよくあるプロパティ。
startsWith
、endsWith
、includes
などのサーチ系もあるが、match
という、正規表現を使用する強力なものもある。
toLowerCase
やtoUpperCase
などのトランスフォーム系、それにconcat
やsplit
などのC++やC#でお馴染みの便利メソッドがあるのが分かる。
Array.prototype
Object.getOwnPropertyNames(Array.prototype)
["length", "constructor", "toString", "toLocaleString", "join", "pop", "push", "reverse", "shift", "unshift", "slice", "splice", "sort", "filter", "forEach", "some", "every", "map", "indexOf", "lastIndexOf", "reduce", "reduceRight", "entries", "keys", "copyWithin", "find", "findIndex", "fill", "concat", "includes"]
C++やC#に慣れている人にとってはpop
やpush
それにconcat
などは軽い驚きを与える。それらの言語では Array は要素数が固定されているの、それをスタックのように使うには別のデータストラクチャーを使わなくてはならない。JavaScriptのArrayはより柔軟だということが分かる。
find
やfindIndex
などのサーチ系は一般的。
forEach
を使ってそれぞれの要素をインデックスなどを使わずに処理することもできる。
要点
built-in オブジェクトのprototypeのプロパティを知ることがJavaScriptプログラミングの基礎になる。
(つづく)