online comment (オンラインコメント)

uupaa.js のコメントは、多くの場合オンラインコメントとして記述しています。

例えば、uu.ajaxGC部分では

    function gc(abort) {
        if (abort) {
            // [IE6][IE7][FIX] xhr.abort throw exception
            // http://twitter.com/uupaa/status/26953945895
            try {
                xhr && xhr.abort && xhr.abort();
            } catch (err) {}
        }
        watchdogTimer && (clearTimeout(watchdogTimer), watchdogTimer = 0);
        xhr = null;
//{@mb
        _gecko && uueventdetach(win, "beforeunload", ng); // [Gecko]
//}@mb
    }

のように、URL( http://twitter.com/uupaa/status/26953945895 )を埋め込んでいます。

http://twitter.com/uupaa/status/26953945895 の中身は次のようになっており、コードの意味と背景が分かるような記述になっています。

IE6, IE7 では xhr.about に触れることが許されないケースがあるので( xhr.about にタッチするだけで例外発生 ) try 〜 catch で囲っとくべきだったか

via http://twitter.com/#!/uupaa/status/26953945895

なぜオンラインコメントなのか

ライブラリやフレームワークのコードはどういった環境や文字コードで利用されるか分からないため、ASCIIコード以外の文字コードは原則的に利用できません。
ASCIIコード以外の文字を使うと、読めないだけではなく、実行時エラーを発生させるおそれがあるからです。

オンラインコメントのメリット

オンラインコメントは、次のような使い方ができます。

  • 日本語のコメントや、より詳細なコメントを望んでいるユーザのために利用できる
  • 資料,画像,映像を活用した説明が可能になる
  • 設計思想など、長くなりそうな場合は、短い英文とURLだけを埋め込み、ブログやツイッターに読み物の形で説明する

IE9RC で document が const になり再定義が不可能になった

IE9RC の挙動を調べています。

IE9RC では @amachang さんの
/*@cc_on var doc = document; eval('var document = doc'); @*/
が封印されてる

http://twitter.com/#!/uupaa/status/35942322231320576

残念ながら、 @amachang さんの発案した方法がIE9で封印されてしまったようです。

IE9RC Release note http://msdn.microsoft.com/en-US/ie/ff468705.aspx

window.document が const になったため、再代入するとエラーになります。
http://jsdo.it/uupaa/Wq0w で試せます。

回避案

(function(document) {
  // ローカルスコープ
  doc.createElement("div");
})(document);

こんな感じにするといいかもしれません。ローカルスコープ内では document にアクセスすると、同じような効果が得られます。
ついでにナビ子記法の導入もおすすめです → http://handsout.jp/slide/1883

ナビ子記法のスライドに書いていない事

あまりメリットを書きすぎると胡散臭いだろうなと思い、スライドには書いてないことがあったりします。

ナビ子記法ならさらに高速化
var lib = {}:
(function(doc) {
  // スコープ内部
  lib.api1 = libapi1; // function
  lib.api2 = libapi2; // function
  lib.api3 = libapi3; // function
})(docuemnt);

スコープ内部では、 lib.api1() として関数を呼び出さずに libapi1() として呼び出すと lib.xxx() 呼び出しに伴うコスト(ドット演算子の名前解決コスト)をカットできます。

ナビ子記法なら、コード圧縮率も向上

さらにコードの圧縮時(Minify)に、lib.api1() といったコードは x.api1() と8文字以下には圧縮できませんが、libapi1() と記述していると x() や xx() のように3〜4文字に圧縮されます。

lib.api1() は圧縮しても、せいぜい x.api1() にしかならない
libapi1() を圧縮すると、 x() や xx() に圧縮される
ナビ子記法ならECMAScriptJScriptの解釈の違いも発生しなくなる

IE6〜IE8のJScriptでは名前付きの匿名関数のスコープの扱いが ECMAScript-262 5th Edition や他のブラウザの実装とは異なるため、JScriptべったりなコードを書くとトラブルが発生する場合があります。

var fn = function fnName() {};

if (typeof fnName === "function") {
    alert( "maybe legacy JScript(IE6, IE7, IE8)" );
    if (fn !== fnName) {
        alert( "??" );
    }
} else {
    alert( "ECMAScript standard" );
}
  • WebKit, Gecko, Opera, IE9 なら "ECMAScript standard" が表示されます。
  • IE6, IE7, IE8 なら、"maybe legacy JScript(IE6, IE7, IE8)" と "??" が表示されます。
    • fn と fnName は何かが違うんでしょうね…

IE6〜IE8 において typeof fn は "function" ですが、ECMAScript 準拠であれば typeof fn は "undefined" になります。

ナビ子記法は、一石七鳥ぐらいある気がするけど

あんまりメリットばかりPUSHすると胡散臭くなるから自重モード

おまけ

uupaa.js では、uu.ready からコールバックされた時の第二引数が document オブジェクトなので、

uu.ready(function(uu, doc) {
  alert( doc.documentMode );
});

最初からナビ子記法のメリットをおいしくいただけます。

おまけのおまけ

第一引数には uupaa.js のルートオブジェクト(uu)が渡されるのですが、これを uu ではなく $ で受け取ると、ちょっとしたデジャブ感を楽しむ事ができます。

uu.ready(function($, doc) {
  $(".hoge").click(function() {
    
  });
});

#ちょっとだけですが

innerHTML = "" まとめ

HTML5をサポートしていないIE6〜IE8で、node.innerHTML = "

...
"; のように HTML5 で追加された新要素含んだ文字列を innerHTML に与えると、次のいずれかの条件が成立した場合に親子関係の崩れたサブツリーを生成してしまいます。

  1. innerHTML の前に document.createElement() が実行されていない
  2. node が DOM Tree に参加していない(オンザフライ)

これらを回避するには、IE6〜IE8で以下のようします。

document.createElement("section"); // HTML5 Shiv

function build(fragment) { // @param HTMLDocumentFragmentString: "<nav>...</nav>"
                           // @return DocumentFragment:
    var div = document.createElement("div");
    div.style.display = "none";
    document.body.appendChild(div); // DOM Tree に参加してから

    div.innerHTML = fragment; // innerHTML を実行する

    var rv = document.createDocumentFragment(); // DocumentFragment に移し替え
    while (div.firstChild) {
        rv.appendChild(div.firstChild);
    }
    document.body.removeChild(div); // DOM Tree から除去
    return rv; // DocumentFragment を返す
}

var documentFragment = build("<section>...</section>");

document.body.appendChild(documentFragment);

innerHTML の代わりに cloneNode() を使った場合は、生成される node の tagName と CSSセレクタでヒットする要素名にズレが発生しスタイルが適用されなくなります。

var div = document.createElement("div");
var section = document.createElement("section");
document.body.appendChild(div);
div.appendChild(section);
section.appendChild(document.createTextNode("..."));

var clonedNode = section.cloneNode(true);
alert( clonedNode.outerHTML ); // "<:section>...</:section>"

document.body.appendChild(clonedNode);

この時、clonedNode は document.getElementsByTagName("section") で検索できるが、 section { color: red } は適用されない状態になります。

CSSを適用するには以下のように、要素名の前に \: を追加したスタイルも設定します。

section,
\:section {
  color: red;
}

innershiv

jQuery ユーザの場合は、オンザフライで、$("div").append("section") のようにすると失敗してしまいます。
これを回避するには、HTML 5 innerShiv を使い、

$("div").append(innerShiv("<section>...</section>"));

のようにします。
innerShiv はDocumentFragmentを返す関数です。
内部では、DOM Tree に参加した状態で、innerHTML を行い、jQuery側で不具合が発生しないように回避します。

$("div").append("section") できないらしいので色々調べてみた

追記: jQ的には解決していませんが、素の JavaScript を使った回避方法がわかりました。IE8以下では、オンザフライで作成した要素に対して innerHTML すると謎要素が作成されてしまうといった現象がでるため、一度要素片を、DOMツリー( body とかね ) にぶらさげてから innerHTML すると回避できました。

つまり、オンザフライな↓ではだめで…
var div = document.createElement("div"); // 作る
div.innerHTML = "<section>ほげー</section>"; // 突っ込む → ι(´Д`υ)

↓のようにすると謎の挙動を回避できます。
var div = document.createElement("div"); // 作る
document.body.appendChild(div); // あてがう
div.innerHTML = "<section>いけたー</section>"; // 突っ込む → ヽ(・ω・)ゝ 

createDocumentFragment() を使っても、オンザフライなノードに突っ込むと、同様に謎の挙動が発生します。

node.cloneNode() は、nodeがオンザフライかどうかに関わらず、謎の <:section> ノードを生成するため、
CSSを適用したい場合は、
section { ... } と記述するだけだとだめで、
section, \:section { ... } と併記する必要があります。 \:section を併記すると IE8 でスタイルが適用されます(IE6, IE7 では適用されません)。

文章で書くとよくわからないと思いますがコードにすると↓こんな感じです。

<!DOCTYPE html><html lang="ja"><head><meta charset="utf-8" />
<title></title></head><body>
<style>
section    { color:blue;outline:2px solid blue }
\:section  { color:pink;outline:2px solid pink } /* IE8で<:section>に適用される, IE6, IE7には適用されない */
\\:section { color:red; outline:2px solid red  } /* IE6, IE7, IE8 には適用されない */
</style>

<script>
document.createElement("section"); // HTML5 siv

var div = document.createElement("div"); // 作る
div.innerHTML = "<section>ほげー</section>";
document.body.appendChild(div); // あてがう → ι(´Д`υ)

var div2 = document.createElement("div"); // 作る
document.body.appendChild(div2); // あてがう
div2.innerHTML = "<section>いけたー</section>";  // ヽ(・ω・)ゝ 

var div3 = div2.cloneNode(true);
document.body.appendChild(div3);
alert(div3.outerHTML); // ※

// ※
// IE9              では <div><section>いけたー</section></div>
// IE8              では <DIV><:section>いけたー</:section></DIV>
// IE7              では <DIV><:section>いけたー</:section></DIV>
// IE6              では <DIV><:section>いけたー</:section></DIV>
// IE9(IE7互換mode) では <DIV><section>いけたー</section></DIV>
// IE9(IE8互換mode) では <DIV><section>いけたー</section></DIV>
</script>
</body></html>

IE9IE7,IE8 互換モードは実際の IE7,IE8 と細かな挙動が異なるのです、いっぱいあって説明する時間も気力もないのですが、ひとつ言えることは、「HTML5 に対応しました。(IE9 の互換モードで)確認もしました(ドヤ)」する人は、もれなく罠にはまるといいよ!

ここまで追記

ここから本文

@hokaccha さんのツイートを見て気になったので、

$('div').append('

hoge
'); とかしたときIEでstyleが効かなくなる件jQuery1.5になったら対応してくれるかなと思ったけど、試してみたらダメだった。

http://twitter.com/hokaccha/statuses/34258257182724098

IE6〜IE8 で HTML5 で追加された新要素(この場合は section) を jQuery で追加するとスタイルが適用されない問題があるらしい。

jQuery で試してみた

jQuery の最新版(1.5)で http://jsdo.it/uupaa/createHTML5ElementByjQuery 試してみた。

<script>
var css = "div{border-top:1px solid blue}";

document.createElement("section"); // HTML5 shiv

$(function() {
    $("head").append("<style>" + css + "</style>");
    $("div").append("<section>section</section>");
});
</script>
<style>
section,
\:section {
    display:block;border-top:1px solid red
}
</style>
<div>div</div>
<div>div</div>

uupaa.js で試してみた

uupaa.js の最新版で http://jsdo.it/uupaa/createHTML5ElementByuu 試してみた。

<script>
var css = "div{border-top:1px solid blue}";

uu.ready(function() {
    uu.head(uu.style(css));
    uu("div").add(uu.section("section"));
});
</script>
<style>
section,
\:section {
    display:block;border-top:1px solid red
}
</style>
<div>div</div>
<div>div</div>

IEのinnerHTMLとcloneNodeの不具合を調査している時に、

IE6〜IE8で(

).cloneNode(true)で生成せした要素にスタイルが当たらなくなるのは、 クローンで作成したノードがチラ見だと
にみえて実は<:section>要素になっているため。

http://twitter.com/#!/uupaa/status/34544812854091776

      • -

jsからは

のようにみえて、実はouterHTMLすると<:section>なので、CSSが当らないし、IEは仲間を呼んだ。爆弾岩が現れた。IEはぱるぷんてを唱えた。みたいなDOMツリーになる

http://twitter.com/#!/uupaa/status/34545606823256064

      • -

cloneNode()せずにNodeの属性を正確に素早くコピーする方法はない。cloneNodeすると

が<:section>になる。outerHTMLに"hoge"突っ込むとDOMツリーの親子関係ぶっこわれ3つのノードができる。手詰まり

http://twitter.com/#!/uupaa/status/34555491514322944

      • -

IE

.cloneNode() した要素にCSSを当てるには \:section { display:block;border-top:1px solid red } のように \:section な感じで先頭にバクスラでエスケープいれればOK

http://twitter.com/#!/uupaa/status/34562498577309696

と色々と無駄知識が手に入ったので、
このデモでは、CSSに section と一緒に \:section を併記し、ライブラリ内部でcloneNodeされている要素にもスタイルが適用されるようにしてあります(結局逃げてますが)。

実行結果

IE8 + jQuery

画面表示
DOMツリー

IE8 + uupaa.js

画面表示
DOMツリー

jQueryの場合、画像のように DOM ツリー構造がそもそも壊れちゃってて、

<div>
  文字列
  <section>
  文字列
  </section>
</div>

のように、本来は2つあれば十分なはずの div 要素の子要素が4つもある。
この状態だとスタイルも適用されなかったんだけど…

どうせ、皆さんが使っているjQuery使えば対策済みですから大丈夫ですよ

HTML5の新要素をinnerHTMLで生成できないバグを回避する - latest log http://htn.to/9wkfZg

http://twitter.com/#!/ofk/status/34472957216555008

ってブクマが今朝のエントリに書かれてたのが気になる。
何か特別な方法があるのかもしれないんだけど、jQユーザではないのでよくわからず。

uupaa.js でも、コードを一部入れ替えると、jQueryのように壊れたDOMツリーが生成されてしまう。

 // uu("div").add(uu.section("section"));        // cloneNode でノードをノードとしてコピーする
    uu("div").add("<section>section</section>"); // innerHTML でノードを文字列としてコピーする

uu.section() でノードを作る場合は、内部で cloneNode が走るので被害を最小限にできるんだけど、
uu.node("

section
") や uu.add() でノードをつくると、内部で innerHTML が走ってしまい、innerHTML の副作用で親子関係が維持されず div の子要素が4つになってしまう。

今のところ、innerHTML で親子関係が崩れる問題をどうにかする良い方法は見つかってない。IE6〜IE8のバグ由来の仕様という認識。

お願い

uupaa.jsで、HTML5で追加された要素を作成する時は、uu.add("

...
") や uu().add("") のように文字列から生成せず、uu.section() や uu.aside() など、予め用意されたノード生成関数で生成し、IEのバグを回避してください。

その際に、CSS には \:section や \:aside など cloneNode で生成される特殊な要素に対するスタイルも併記してください。
よろしくお願いします。

CSSの\:sectionの話については、こちらをどうぞ http://jsdo.it/uupaa/IEcloneNodeBug (HTML5の新要素をcloneNodeするとCSSが適用されないバグを対策する)

HTML5の新要素をinnerHTMLで生成できないバグを回避する

IE6〜IE8のinnerHTMLには問題が多く、そのひとつに、HTML5の新要素を食わせると悪夢のようなDOMツリーを生成するというものがあります。

var div = document.createElement("div");

div.innerHTML = "<section>section</section>";
document.body.appendChild(div);

上記のコードを実行すると、body以下にはこのような構造ができてしまいます。

<body>
  <div>
    section
    </SECTION>
  </div>
</body>

どうやら、innerHTML に与える文字列を div でラップし、div要素の前に何か一つ以上のダミーの文字列を設定しておき、あとでアンラップすれば上手くいくようです。

http://jsdo.it/uupaa/IEInnerHTMLBug で試せます。

window.onload = function() {
    document.createElement("section"); // HTML5 siv
    
    var div = document.createElement("div");
    
    div.innerHTML = "<section>section</section>";
    
    alert( div.innerHTML ); // "section</SECTION>" in IE6〜IE8
    
    // 対策するには、
    // ダミーの<div>でラップし、あとでdivを除去します
    if (document.uniqueID) { // is IE
    
        var div = document.createElement("div");
        div.innerHTML = "*<div>" +
                        "<section>section</section>" +
                        "</div>";
        alert( "wrapped div.innerHTML = \n" +
                div.innerHTML );
                
        div = div.lastChild; // unwrap                
        alert( "unwraped div.innerHTML = \n" +
                div.innerHTML );
    }
};

RegExp#test + parseInt vs RegExp#exec + plus operator

CSSValue な "12em" や "123px" から 単位がpx の場合に 123 という数値を素早く取り出すには、

  1. RegExp#test でテストしてから parseInt で取り出す
  2. RegExp#exec してから +(matchedValue) で数値として取り出す

のどちらが効率的なのか気になったのでベンチマークとってみました。

RegExp#test()
+ parseInt()
RegExp#exec()
+ parseInt()
RegExp#exec()
+ plus operator
loops
IE9pp7 861 2134 2072 100
IE8 410 917 860 10
GC9 410 564 445 100
Op11 670 803 726 100
Fx40β10 337 1488 1566 100
iPhone3GS
(iOS4.2)
138 333 344 1
HTC Desire HD
(OS2.2)
48 78 52 1

RegExp#test() してから parseInt すると良いようです。

jsdo.it で試せます http://jsdo.it/uupaa/6LQq

<!DOCTYPE html><html lang="ja"><head><meta charset="utf-8" />
<title>bench: RegExp#test + parseInt vs RegExp#exec + plus operator</title>
<style>th,td { padding: 5px; text-align: right }</style>
<script src="bench.js"></script><script>

/*
    RegExp#test() + parseInt()
            vs
    RegExp#exec() + plus operator
 */

window.onload = function() {
    document.body.innerHTML += "<p>" + navigator.userAgent + "</p>Running...";

    var loop = getLoop();

    // ---------------------------------------------------------
    var rex1 = /\d+px$/,
        rex2 = /(\d+)px$/;

    function _test(idx) {
        var patt = idx + "px";

        if (rex1.test(patt)) {
            return parseInt(patt);
        }
        return idx;
    }

    function _exec(idx) {
        var patt = idx + "px";
        var match = rex2.exec(patt);

        if (match) {
            return parseInt(match[1]);
        }
        return idx;
    }

    function _exec2(idx) {
        var patt = idx + "px";
        var match = rex2.exec(patt);

        if (match) {
            return +match[1];
        }
        return idx;
    }

    var arg;
    job(5, loop, ["RegExp#test() + parseInt()",     _test,
                  "RegExp#exec() + parseInt()",     _exec,
                  "RegExp#exec() + plus operator",  _exec2,
                  "RegExp#exec() + parseInt()",     _exec]);
}
</script></head><body></body></html>

地獄のJavaScript2

(ε・◇・)з 戦いはこれからだ!

http://jsdo.it/uupaa/hellsjs2 で動かせるよ

// forked from uupaa's
//   "地獄のJavaScript (Symbolic JavaScript)"
//   http://jsdo.it/uupaa/hellsjs

uu.ready("window", function(uu, doc) {
  uu.mix(window, uu);
  uu.config.log.rollup = 1000; // logロールアップ回避

  // use: $=_-+~[]{}()*/<!\;"
  $         =  -~[];        // 1
  $$        =  $+$;         // 2
  $$$       =  $$+$;        // 3
  $$$$      =  $$$+$;       // 4
  $$$$$     =  $$$$+$;      // 5
  $$$$$$    =  $$$<<$;      // 6
  $$$$$$$   =  $$$$$$+$;    // 7
  $$$$$$$$  =  $$<<$$;      // 8
  $$$$$$$$$ =  $$$*$$$;     // 9
  _         =  +[];         // 0
  __        =  // "constructor"
               (""+{})[$$$$$] +  // c
               (""+{})[$] +      // o
               (""+$/_)[$] +     // n
               (""+![])[$$$] +   // s
               (""+!![])[_] +    // t
               (""+!![])[$] +    // r
               (""+[][[]])[_] +  // u
               (""+{})[$$$$$] +  // c
               (""+!![])[_] +    // t
               (""+{})[$] +      // o
               (""+!![])[$];     // r
  ___       =  // "return "
               (""+!![])[$] +    // r
               (""+!![])[$$$] +  // e
               (""+!![])[_] +    // t
               (""+[][[]])[_] +  // u
               (""+!![])[$] +    // r
               (""+$/_)[$] +     // n
               " ";
  ____      =  // "self"
               (""+![])[$$$] +   // s
               (""+!![])[$$$] +  // e
               (""+![])[$$] +    // l
               (""+![])[_];      // f
  _____     =  // "proto"
               (""+/ /[__])[$$$$$$$*$$] + // p
               (""+!![])[$] + // r
               (""+{})[$] + // o
               (""+!![])[_] + // t
               (""+{})[$]; // o
  ______    =  // "type"
               (""+!![])[_] + // t
               (""+$/_)[$$$$$$$] + // y
               (""+/ /[__])[$$$$$$$*$$] + // p
               (""+!![])[$$$]; // e

  $_        =  ""[__][__];       // new Function
  $$_       =  $_((___+____))(); // new Function("return self")() -> DOMWindow

/*
   8                = $$<<$$;
   9                = $$$$$$$$$;
   10               = $$$$$<<$;
   20               = $$$$$<<$$;
   new Function     =  "".constructor.constructor
  "function RegExp() { [native code] }"
                            25
                    =  / /[__];
  "function Number() { [native code] }"
                    =  (0)[__];
   undefined        =  [][0];
   true             =  !![]; // or  !+[]
   false            =   ![]; // or !!+[]
   NaN              =   -{}; // or   +{}
   Infinity         =  1/0;
  "[object Object]" =  (""+{})
  -1                =  ~[];
   0                =  +[]; // or -[] // or +""
  -2147483648       =  -1 << -1;
   2147483647       =  ~(-1 << -1);
 */
  log('"a"   = @', (""+![])[$] );
  log('"b"   = @', (""+{})[$$] );
  log('"c"   = @', (""+{})[$$$$$] );
  log('"d"   = @', (""+[][[]])[$$] );
  log('"e"   = @', (""+!![])[$$$] );
  log('"f"   = @', (""+![])[_] );
  log('"g"   = @', (""+/ /[__])[$$$$$*$$+$] ); // 11
  log('"i"   = @', (""+$/_)[$$$] );
  log('"j"   = @', (""+{})[$$$] );
  log('"l"   = @', (""+![])[$$] );
  log('"m"   = @', (""+(_)[__])[$$$$$*$$+$] ); // 11
  log('"n"   = @', (""+$/_)[$] );
  log('"o"   = @', (""+{})[$] );
  log('"p"   = @', (""+/ /[__])[$$$$$$$*$$] ); // 14
  log('"r"   = @', (""+!![])[$] );
  log('"s"   = @', (""+![])[$$$] );
  log('"t"   = @', (""+!![])[_] );
  log('"u"   = @', (""+[][[]])[_] );
  log('"v"   = @', (""+/ /[__])[$$$$$$*$$$$+$] ); // 25
  log('"x"   = @', (""+/ /[__])[$$$*$$$$+$] ); // 13
  log('"y"   = @', (""+$/_)[$$$$$$$] );
  log('"E"   = @', (""+/ /[__])[$$$*$$$$] ); // 12
  log('"I"   = @', (""+$/_)[_] );
  log('"N"   = @', (""+(+{}))[_] );
  log('"O"   = @', (""+{})[$$<<$$] ); // 8
  log('"R"   = @', (""+/ /[__])[$$$*$$$] ); // 9
});