Re:Start jQuery #003

JavaScriptに関して

ちょっとググってみると、JavaScript(ECMA5)にはクラスが無いからダメだと言ってる人がかなり多いみたいで、そこまでクラスに拘る意味が俺としては良くわからないんだけど、フランスにいったら誰も日本語を話せないからフランス人は全員がクソだって言ってるのと同じくらい、どうでも良い主張だと思ってるんだよ。

そして、プロトタイプという継承方法があるにもかかわらず、どうしてもクラスみたいに書きたいらしい。

すでに医学書と言えばエゲレス語に変わりつつあるのに、苦労してオランダ語で医学を覚えた蘭学の学生が意地を張って『わしは読みたい本があったらオランダ語で読む!!』なんて口からクソ吐いちゃってるシーンがある。手塚治虫の『陽だまりの樹』って漫画。めちゃめちゃ面白いよ。

プロトタイプベースが嫌いならJavaScript使うのやめれば?とか言われたら、クラス至上主義の方々ってどうするつもりなんだろうね。

Z80とかMSX2のVDPをマシン語でいじってたときに比べると、今のこういった彼ら彼女らの主張とかってもとより興味がないんで、議論するつもりも当然無いし、Disられても俺はスルーしている。だいたいね、みっともないんだよね。

というわけで、俺はどっちでもいいんだ。
ただ、そばをスプーンで食べるようなことはしたくない。作法というものがあるのでね。

さて、本題。

今回はいったんjQueryから離れて、JavaScriptに関するネタを復習しておきたい。

プロトタイプに関して

JavaScriptのプロトタイプとは、自分が持ってるプロパティやメソッドを共通化できる仕組みだ。
ただそれだけだ。

もちろん__proto__っていう入れ物に入ってないといけないけどね。

で、もし呼ばれたプロパティを自分が持ってなかったら、自分の親のプロパティを見る。nullが見つかるまでそれを繰り返す。
ただそれだけだ。

ちなみにこの親をたどる仕組みには名前がついていて、プロトタイプチェインと言う。

そしてこれは大事なこと何だけど、__proto__というのは標準仕様じゃなくて、ブラウザ依存。たまたま多くのブラウザが対応してしまっているので使えてしまうという現実。

じゃぁ__proto__がない場合ってどうすんの?と思ったけど、おそらく本体である[[prototype]]を直接いじるんじゃないかなぁ。よくわかってないけどさ。

コンストラクタに関して

new演算子で関数を呼ぶと、その関数はコンストラクタとして呼ばれたあと、新規オブジェクトを返す。
以下の例だと新規オブジェクトをobjに代入している。

この時、myClassprototypeの中身が、obj__proto__にコピーされる。

試してみる

  • obj → { name: ‘Ogaaaan’ }
  • obj.prototype → undefined
  • obj.__proto__ → オブジェクト

__proto__の中にはコンストラクターと__proto__が入っている。つまりobj.__proto__.__proto__という階層になってる。

__proto__.__proto__の中身は?

obj.__proto__.__proto__の中には、以下のメソッドが含まれていた。

  • __defineGetter__
  • __defineSetter__
  • __lookupGetter__
  • __lookupSetter__
  • constructor
  • hasOwnProperty
  • isPrototypeOf
  • prototypeIsEnumerable
  • toLocaleString
  • toString
  • valueOf
  • get __proto__
  • set __proto__

それぞれ何をするのかと言うとこんな感じ。

  • __defineGetter__
    • 非標準|非推奨 プロパティが参照される時にプロパティと関数を紐付ける
  • __defineSetter__
    • 非標準|非推奨 プロパティに値が代入される時に紐付けられてる関数を呼ぶ
  • __lookupGetter__
    • 非標準|非推奨 プロパティに紐づいてるgetter関数を返す
  • __lookupSetter__
    • 非標準|非推奨 プロパティに紐づいてるsetter関数を返す
  • constructor
    • コンストラクタ自身の参照
  • hasOwnProperty
    • 自分自身がそのプロパティを所有してるかどうかを返す
  • isPrototypeOf
    • オブジェクトが別のオブジェクトのプロトタイプチェーンに存在するかを返す
  • prototypeIsEnumerable
    • 指定されたプロパティが列挙可能(for inで拾えるか)かどうかを返す
  • toLocaleString
    • toStringの国際化
  • toString
    • 与えられた変数を文字列として返す
  • valueOf
    • オブジェクトのプリミティブ型([object Object]みたいなやつ)を返す
  • get __proto__
    • ゲッター
  • set __proto__
    • セッター

これらのメソッドはすべてのオブジェクトに継承される。

非標準、非推奨のやつは、すでにdefinePropertyというメソッドがECMA5で定義されてるので、使いたくなかったらそっちを使えばよい。

prototypeは?

prototypeは、__proto__に入れておきたいものを代入するときに使う。

これで、

  • コンストラクタ
  • __proto__

のほかに、

  • duel

も含まれるようになる。

なんで直接やり取りしないのかと言うと、直接やり取りすると全部のメソッドを継承してしまうから。
継承したいメソッドだけを__proto__に入れるため、prototypeにいったん代入してるらしい。

どうしてもクラス宣言の中で__proto__にメソッドを入れたかったら、prototypeではなく__proto__に直接代入すればできるけど、これが果たして正しいのかどうかは不明。

ちなみに以下の方法だと動くには動くが、自分のオブジェクトのみに宣言しているので、当然__proto__duel()が入らない。つまりduel()は再利用できなくなり、再利用したくても新たに同じメソッドを書かなくてはいけなくなる。無駄だ。

いつどんな風につかうのか

  1. 引数に与えたパラメータを自身のパラメータ(つまりプロパティ)に初期化するコンストラクタmyClassを用意
  2. myClass__proro__duel()関数を定義
  3. myClassを具現化したオブジェクトobjを定義
  4. objを元にcldオブジェクトを生成
  5. cldduel()メソッドを呼び出す → 正しく呼ばれる

ソースはこんな感じになる。

つまり、myClassduel()はコンストラクタでobjに具現化され、objを継承したcldで実行可能状態、と言うことになる。

こんな風なコードも掛ける。