Chromeのconsole.logの謎

この記事を読んで、Chromeのconsole.logの動きが謎すぎる気がして確認してみました。
CoffeeScriptで、newするときのプロパティ変数の初期化ってどーなってんの? - uzullaがブログ

検証

ソースコード
var a = [];

a.push('A');
console.log(a);

a.push('A');
console.log(a);

これをChromeのDeveloper toolのコンソールで実行してみました。

期待する出力
["A"]
["A", "A"]
実際の出力
["A", "A"]
["A", "A"]

調べてみた

console.log lazy等でググるとStackoverflow等のサイトがでてきた。
Is Chrome's JavaScript console lazy about evaluating arrays? - Stack Overflow
javascript - Bizarre console.log behaviour in Chrome Developer Tools - Stack Overflow
35801 – Web Inspector: generate preview for the objects dumped into the console upon logging.
なるほど。Webkit特有なのかもしれません。
console.logにオブジェクトを渡すと、console.logはそのオブジェクトの内容を出力します。
console.logが出力する準備をしている間にそのオブジェクトに変更を加えると変更後のオブジェクトの内容が出力されると。
シンプルな解決方法として

  • JSON.stringify
  • toString
  • slice (配列の場合)

等のメソッドや関数を使って別のオブジェクトを作成してから渡す方法が挙げられてますね。

ちなみに

Coffeescriptのクラスのプロパティ変数って、初期化が必須なの?(;´Д`)
コンストラクタで宣言しなおさないと、暗黙のウチに共有されちゃうの?
そういうもんなの??

JavaScriptのprototype継承ですね。

class Chain
  queue: []
  test: -> @queue.push "A"

var Chain;
Chain = (function() {
  function Chain() {}
  Chain.prototype.queue = [];
  Chain.prototype.test = function() {
    return this.queue.push("A");
  };
  return Chain;
})();

JavaScriptになるとこうなっています。
Chainクラスはprototypeというオブジェクトを持っています。(以下prototypeオブジェクト)
CoffeeScriptのクラスのメソッド等はこのprototypeオブジェクトのプロパティに定義されます。
Chainクラスのインスタンスはprototypeオブジェクトへの参照を持っています。

Chainクラスのインスタンスからqueueプロパティにアクセスするとき

  1. まずインスタンス自身がqueueプロパティを持っていないか調べる
  2. 持っていなければクラスのprototypeオブジェクトにqueueがないか調べる。
  3. クラスのprototypeオブジェクトにもqueueプロパティがなければ更に継承元のクラスを辿って行く

という感じになってます。

インスタンス自身がプロパティを持っているかどうか調べるためにはhasOwnPropertyを使います。

ソースコード

class Chain
  queue: []
  test: -> @queue.push "A"

c = new Chain
console.log "before c.queue = []"
console.log c.hasOwnProperty "queue"

c.queue = []

console.log "after  c.queue = []"
console.log c.hasOwnProperty "queue"
実行結果
before c.queue = []
false
after  c.queue = []
true

queueプロパティに配列を代入するまで、Chainクラスのインスタンスは自分自身のqueueプロパティを持ちません。
そのため、全てのインスタンスがChainクラスのprototypeオブジェクトのqueueプロパティを参照しています。
なのでtestメソッドの中でthis.queue.push("A")を呼び出すと、Chainクラスのprototypeオブジェクトのqueueプロパティが変更されるわけです。

こう書くとなおるわけ

ソースコード

class Chain
  constructor: -> @queue = []

console.log "c = new Chain"
c = new Chain
console.log "c.hasOwnProperty 'queue': #{c.hasOwnProperty 'queue'}"

JavaScriptになると

var Chain, c;
Chain = (function() {
  function Chain() {
    this.queue = [];
  }
  return Chain;
})();

console.log("c = new Chain");
c = new Chain;
console.log("c.hasOwnProperty 'queue': " + (c.hasOwnProperty('queue')));
実行結果
c = new Chain
c.hasOwnProperty 'queue': true

コンストラクタの中で以下のコードを呼び出すことで、インスタンス自身のプロパティとしてqueueを定義しています。

this.queue = [];

インスタンス自身のプロパティなので、ほかのインスタンスのqueueを変更しても、関係ない。
そんな感じ。

まとめ

  • Webkitのconsole.logはオブジェクトが表示されるまでにディレイがあって、その途中でオブジェクトの中身を変更すると、変更後の内容が出力される
  • CoffeeScriptのクラスの仕組みについて知るためにはJavaScriptのプロトタイプチェーン等に関する知識が必要。

JavaScriptについて勉強するにはパーフェクトJavaScriptがお勧めです。

パーフェクトJavaScript (PERFECT SERIES 4)

パーフェクトJavaScript (PERFECT SERIES 4)