Kanasan.JS JavaScript 第5版読書会 #2 雑感

やはり能動的な目的(本を読む!)があるというのは良いもので、人の話聞いてるだけの勉強会よりずっと充実した一日に思えました。議論は絶えないしそれを眺めているだけでも楽しかった。

サイ本は2年くらい前にざっと通読した程度ですが、当時はちゃんと分かってなかった部分も多かったので復習を兼ねて参加しました。コアの部分はそれほど変わっていませんが、結構忘れていたこと多かったですし言語仕様の細かいところをちゃんと抑える良い機会になりました。

あと、いろんな人とお話できたのが良かったです。勉強会後の懇親会の方がむしろ面白い話を聞けたかもしれない。id:amachangは、すごく実直な方だなぁと思った。

40人を越える大所帯となると、仕切る側がとても大変そうでした。運営陣の皆様、本当にありがとうございました。
おそらく次回が山場になる(関数とかプロトタイプとか)と思うので、今から非常に楽しみです。

prototypeの話

勉強会中に混乱を巻き起こしていた話題ですが、自分の中でもわりと曖昧だったので次回の予習として整理しておきます。間違い等あればご指摘ください。

あるオブジェクトのプロパティへのアクセスは、まずそのオブジェクト自身のプロパティを探し、見つからなければそのオブジェクトのコンストラクタのprototypeプロパティに設定されたオブジェクトを見にいきます。

function A(){}
A.prototype.foo = function() { alert('foo!') };
var a = new A();
a.foo == A.prototype.foo; // => true
a.foo == a.constructor.prototype.foo; // => true

つまり、aの継承元へプロパティを探しにいこうと思ったら、Aのprototypeプロパティを知る必要があるわけです。

そのオブジェクトは、a.constructor.prototype によって参照することができますが、残念ながら constructorプロパティはaの直接のプロパティではありません。

a.hasOwnProperty('constructor'); // => false
A.prototype.hasOwnProperty('constructor'); // => true

そのため、a.constructorを参照するためにはaの継承元を知る必要があることになり、堂々巡りになってしまいます。

これを解決するには、a.constructor.prototypeにあたるオブジェクトがaの直接のプロパティとして参照できれば良いのです。

実際、Firefoxの場合はそのためのプロパティが "__proto__" という名前で公開されています。

function A(){}
var a = new A();
a.hasOwnProperty('__proto__'); // => true
a.__proto__ == a.constructor.prototype; // => true

ECMA仕様として明示されているわけではないし、IEOperaではこのプロパティにはアクセスできませんが、内部的には同等のものを持っていると考えて良いようです。

というわけで私は、上位層のプロパティを探す場合にはこの__proto__プロパティ相当のものをて辿っている (a → a.__proto__ → a.__proto__.__proto__ → ...) と理解しています。図で見ると分かりやすいです。

// obj[prop] のイメージ
function getProperty(obj, prop) {
  if (obj == null)
    return undefined;

  if (obj.hasOwnProperty(prop))
    return obj.getOwnProperty(prop);

  return getProperty(obj.getOwnProperty('__proto__'), prop);
}

これを直接いじってプロトタイプチェーンを制御することができます。

function A() {}
var a = new A();
a.__proto__ = null;
a.constructor === undefined; // => true

代入時に循環チェックとかしているみたい。

var a = new A();
var b = new A();
var c = new A();
a.__proto__ = b;
b.__proto__ = c;
c.__proto__ = a; // => cyclic __proto__ value

ちなみに、このプロパティは for/in ループとかには出てきません。

for (var p in a)
  if (p == '__proto__')
    alert('ここは呼び出されない');

関数定義時の処理

constructorプロパティだけを持つprototypeプロパティを生成

function A() {}
// 定義時に↓が自動的に行われる
// A.prototype = { constructor: A };

new時の処理

領域確保して、各種プロパティを設定して、コンストラクタで初期化して、コンストラクタが何か値を返す場合はそれをそのまま返す。

// イメージです。
function new(ctor, args) {
  var obj = {};  // 空のオブジェクトを生成

  // 初期化
  obj.__proto__ = ctor.prototype;
        : // 他にもいろいろ

  return ctor.apply(obj, args) || obj;
}

次のようなプログラムを動かしてみると、コンストラクタが適用される前に既に__proto__プロパティが設定されていることが分かります。

 function A() { alert(this.__proto__ == A.prototype); }
 var a = new A(); // 'true'