for in 文の処理について(続編)

先日のエントリJavascriptのfor in文について

for in文は処理済みのプロパティ名のみを覚えていてプロパティの数はチェックしない。未処理のプロパティが存在するか否かはループ内の処理が終わるたびにその時点でのプロパティと処理済のプロパティを比較して判断される。

と、結論付けていたのですがこれとは異なる挙動になる場合をみつけたので続編です。よろしければ先のエントリに目を通してからお読みいただければと思います。

それはどういう場合かというと
「最初に存在していたプロパティを未処理の段階で削除し、ループの次の周でもう一度同じ名前のプロパティを追加した場合」
です。

var testHash = {
	a: "one",
	aa: "two",
	aaa: "three",
	aaaa: "four",
	aaaaa: "five",
	aaaaaa: "six",
	aaaaaaa: "seven"
};
		
(function(){
	var pToTreat = "";         //いったん削除して、次の周で追加するプロパティ
	var count = 0;             //for in文を何周したか数える
	for (var key in testHash){
		count++
		if(pToTreat){                     //最初の周以外はその前の周で削除したプロパティを追加する
			testHash[pToTreat] = "any";
		}
		
		pToTreat = (key == "aaaaaaa") ? "a" : key + "a"; 
		
		document.write(key + ":" + testHash[key] + "<br>"); //キーと値を表示
		
		delete testHash[pToTreat];       //処理した項目より"a"が一つ多い項目を削除
	}
	
	document.write("-------------<br>")        //これ以下は確認用の表示で直接関係ありません
	
	for (var key2 in testHash){
		document.write(key2 + ":" + testHash[key2] + "<br>");
	}
	document.write(count);
})();

どういうことをしているかというとfor in文で最初に"a"というプロパティが処理されたとすると、その周で"aa"を削除します。
次に別のプロパティが処理される際に再び"aa"を追加(戻す)わけです。その後処理されたプロパティに"a"をたしたプロパティを削除し、次の周で戻すということを繰り返します。
これの実行結果は

a:one
aaa:three
aaaaa:five
aaaaaaa:seven
-------------
aa:any
aaa:three
aaaa:any
aaaaa:five
aaaaaa:any
aaaaaaa:seven
4

となります。

もし先日のエントリでまとめた

未処理のプロパティが存在するか否かはループ内の処理が終わるたびにその時点でのプロパティと処理済のプロパティを比較して判断される。

であれば処理されなければならないはずの"aa"や"aaaa"が処理されていません。

これはもう仕様書を読むしかない。
2011年6月にリリースされたECMAScript ver5.1が最新のようなのでそのfor in文の項目をみてみます。91ページからfor in文の記述があり、92ページにループ中の追加と削除についてかかれています。

The mechanics and order of enumerating the properties (step 6.a in the first algorithm, step 7.a in the second) is not specified. Properties of the object being enumerated may be deleted during enumeration. If a property that has not yet been visited during enumeration is deleted, then it will not be visited. If new properties are added to the object being enumerated during enumeration, the newly added properties are not guaranteed to be visited in the active enumeration. A property name must not be visited more than once in any enumeration

(プロパティをループする仕組みと順番は指定されない。ループ中のオブジェクトのプロパティはループ中に削除されてもさしつかえない。もしこのループにおいてまだ走査されていないプロパティが削除された場合、そのプロパティは走査されない。もしループ中にループされているオブジェクトに新たなプロパティが加えられた場合、新たに加えられたプロパティは動作中のループにおいて走査されることを保証されない。いかなるループにおいても同一のプロパティ名が二度以上走査されてはならない)

http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf

なんと。そもそもプロパティが追加された場合そのプロパティを走査の対象とするかどうかは仕様では決まっていないようです。(保証されないということが決まっている、というべきかもしれません。)
ただ「保証されない」のだから原則やってはいけないということのようです。

あらためてまとめ

for in文はループ中にプロパティが追加された場合、そのプロパティを走査の対象とするが一度削除されたプロパティ名と同じ場合は走査されない。
ただ、そもそもループ中のオブジェクトにプロパティを追加することは保証対象外なので「やってはいけない」。

という結論でした。