今回のスクリプトの肝は、HTMLHeadingElementたちを"document order"で生成するジェネレータと、そのジェネレータのnext()をfor each文で呼んでツリー構造を持ったリストを作成する関数とを分離し、よりパフォーマンスの高いジェネレータを選択可能にした点にある。
document.createTableOfContents = function(// may throw StopIteration when there is no headings.
/* Headings generator */headings){
var h = headings.next();
return UL(h);
function UL(h){
var ul = document.createElement("UL");
var li = LI(h);
ul.appendChild(li);
for each (let h_ in headings) {
if (h.level == h_.level) {
li = LI(h_);
ul.appendChild(li);
} else if (h.level < h_.level) {
li.appendChild(UL(h_));
} else {
break;
}
}
return ul;
}
function LI(h){
var li = document.createElement("LI");
li.appendChild(document.createTextNode(h.textContent).linkTo(h));
return li;
}
};
ただし見出し要素を生成するジェネレータは、前回yieldした見出し要素を保持し、見出しレベルが上位に変化した場合にもう一度同じ見出し要素をyieldしなければならない。上記のメソッド内で、見出しレベルの上位への変化があった際、見出しひとつが余分に「消費」されてしまうからだ。カスタムイテレータなら恐らく美しく書けるが、ジェネレータの方が比較的容易に書くことができるためこの方法を採用した。
BODY要素の直下に見出し要素がフラットに並んでいるなら、ジェネレータは次のようにnextSiblingプロパティを使って書ける。
function headings(){
var currentNode = document.body.firstChild;
var prevHeading = null;
do {
if (currentNode instanceof HTMLHeadingElement) {
yield currentNode;
if (prevHeading && prevHeading.level > currentNode.level)
yield currentNode;
prevHeading = currentNode;
}
} while (currentNode = currentNode.nextSibling)
}
ジェネレータはこの関数をコールして生成されたオブジェクトである点に注意する。
document.createTableOfContents(headings); // error
document.createTableOfContents(headings()); // ok