Zepto で script タグを挿入するとハマる

jQuery と Zepto の .append で文字列の script タグを挿入した場合、前者はスクリプトが実行されるのに、後者は実行されなかったので原因を調べてみました。1

問題のコードは次のようなコードです。

$(document.body).append('<script src="' + someUrl + '"></script>');

Zepto の .append

Zepto の実装の方がシンプルなので、まずはこちらから説明します。

  1. 一時的にコンテナとなる div 要素などを用意し、その innerHTML に文字列を代入する cf. zepto.js#L136-L137
  2. div の子要素を全て removeChild で取り除きながら配列に格納する cf. zepto.js#L138-L140
  3. 配列にある要素全てを insertBefore で挿入する cf. zepto.js#L836
  4. 挿入する際に、src なしの script タグだった場合は eval でスクリプトを実行する cf. zepto.js#L837-L839

つまり、script タグを挿入する処理を簡略化すると次のような処理になっています。
次のコードは someUrl のスクリプトを実行しません。

var container = document.getElementById('container');
var html = '<script src="' + someUrl + '"></script>';
var div = document.createElement('div');
div.innerHTML = html;
var script = div.removeChild(div.firstChild);
container.appendChild(script);

4 番目の処理があるため、src を指定していないスクリプトだと Zepto でも実行されます。

jQuery の .append

jQuery の .append はブラウザの互換性のためか、かなりトリッキーなことをしています。

  1. script タグの type を書き換えて、要素を挿入しても JavaScript が実行されないようにする cf. manipulation.js#L664
  2. HTMLElement#appendChild で要素を挿入する cf. manipulation.js
  3. 挿入済みの script タグの type を元に戻す cf. manipulation.js#L688
  4. script タグを HTMLDocument#createElement で生成 cf. ajax/script.js#L46
  5. script タグを head の最初に挿入することでロードする cf. ajax/script.js#L81
  6. script のロードが完了したら head から script タグを取り除く cf. ajax/script.js#L66

最初の script タグの生成方法についてはしっかり読んでないですが、最初に生成した script タグはスクリプトの実行に使われていないので気にしないことにします。
最後の方に、動的に JS を読み込む一般的な方法を使っているので問題なく実行されます。

何故 innerHTML で script タグを生成すると実行されないか?

http://www.w3.org/TR/html5/scripting-1.html#the-script-element には次のように書いてあります。

When inserted using the document.write() method, script elements execute (typically synchronously), but when inserted using innerHTML and outerHTML attributes, they do not execute at all.

また、HTMLDocument#createElement で生成した script タグは async 属性が true になるのに対し、innerHTML で生成した script タグは false になるのも関係してそうです。2
内部的には force-async flag とかその辺でしょうか。
興味のある方は仕様をよく読んでみてください!

以上、Zepto で script タグを挿入する場合は気を付けましょう!!

  1. 正確には .html ですが内部ではどちらも .append が呼ばれるので .append にしておきます 

  2. ちなみにこの属性は後で変更しても効力を持たない仕様みたいです