※ 超長文注意!
動的スクリプトローディング(さんざん既出だと思うけど - IT戦記が、さっぱりわかんなくて凹んだ。
でも、いろいろとJSのエッセンスが詰まってそうな気がしたので、頑張って読んでみた。
で、一応わかったつもりにはなれたので説明してみる。
(駄文だけど僕と同じレベルの人にはもしかしたら役に立つかもしれません)
非常に長いですが、よろしければどうぞ。
では、早速始めます。
[引用]
まずは、全体を引用。
(コピーしにくいように一応行番号を入れてます。使わせてもらうなら、せめて本家を訪問するのが礼儀かと。)
1: var load = function(src, check, next) {
2: check = new Function('return !!(' + check + ')');
3: if (!check()) {
4: var script = document.createElement('script')
5: script.src = src;
6: document.body.appendChild(script);
7: setTimeout(function() {
8: if (!check()) setTimeout(arguments.callee, 100);
9: else next();
10: }, 100);
11: }
12: else next();
13: };
はい、さっぱりわかりませんね(>_<)
僕もそうでした(す?)。
ってことでこれから、1行ずつ見ていきまーす。
(全ての行を解説します。bornnnetbornneetは初心者を見捨てません!)
[はじめに]
(一応説明)この「関数は外部jsファイルを読み込んで、ロードが完了するのを待った後、指定の関数を実行する」というものです。
(説明が下手ですいません)
実際に使っている場面を想定したほうが(僕は)わかりやすいので、以下の解説では、jQueryを読み込んだ後、alertでバージョンを表示するという体で話を進めます。
つまり、
load(
'http://ajax.googleapis.com/ajax/libs/jquery/1.2.6/jquery.min.js',
'window.jQuery',
function() { alert($.fn.jquery); }
);
という呼び出しをしているってことです。
jQueryはAJAX Libraries APIから読み込んでいます。
[本題]
前置きがかなり長くなりましたが、コードリーディング(1回言ってみたかった!)に入ります。
1, 13行目
1: var load = function(src, check, next) {
13: };
1(~13)行目では、外部jsのロードを行う無名関数を定義して、loadという変数に代入しています。
引数は3つで、それぞれの意味は
src | 外部jsのURLやパス |
check | 読み込み完了を判断するために、存在の有無を確認するオブジェクト |
next | 完了後に実行したい関数 |
となっています。
わかりにくいのは、checkですね^^
この関数では、外部jsの読み込み完了を判断する必要がありますが、JSにはそんな機能はありません。
(っていうか外部ファイルのロード自体もHTMLを利用して実現してるぐらいです)
ただ、多くのjsファイル(ライブラリは特に)読み込まれるとグローバル領域に何かしらのオブジェクトを作ります。
なので、そのオブジェクトの有無をチェックすることで、外部jsのロード完了をしろうというわけです。
jQueryだと、window.jQueryがそのオブジェクトになります。(window.はグローバルを示します)
function load(src, check, next) {
}
と書かないのは、前者の方が汎用性が高いためだと思われます。
if文等ブロック内に書けたりとかするので。
(function文だと関数の中に書けないからこっちの形にしてるんだと思ったけど、今回調べてみたらそんな情報は見当たらなかったので勘違いだったみたい)
あと、コピペして使うことを考えるとfunction式にしてた方が使い勝手がいいとか、単にスマートでカッコいいとかの理由で僕もこの方が好きです。
2行目
2: check = new Function('return !!(' + check + ')');
2行目はけっこう重要です。
処理としては、引数のcheckで指定されたオブジェクトがグローバル領域に存在するかどうかをチェックする関数を作成して、checkに代入するということをしています。
まず、!!ですが、これはオブジェクトの有無をbooleanに変換するテクニックです。
また、()で括っているので、引数checkの内容を確実に式として評価することができます。
こうすることで、オブジェクトがあるならtrue、ないならfalseという結果を得られます。
evalを使っても同じことができますが、この方が簡単です。
また、window[check]とかだとメソッドまで確認することができないので、やはりこの方法がいいんでしょう。
そして、Functionコンストラクタで定義された関数のスコープはグローバルになるので、グローバル領域に指定のオブジェクトがあるかどうかを知ることができます。
つまり、今回の例だと、
function() {
return !!(window.jQuery);
}
という関数をグローバルに作ったイメージです。
さらに、この関数はここで実行されるわけではなく、checkという変数に代入されるので、setTimeoutに渡す無名関数の中でも使うことができるのです。(詳細後述)
3, 12行目
3: if (!check()) {
12: else next();
ここは簡単です!
3行目では、さっき作った関数を呼び出して、既にその外部jsが読み込まれていないかを確認します。
指定のオブジェクトがあった場合は、12行目に飛び、実行したい処理を行なって、このload関数は終了します。
4~6行目
4: var script = document.createElement('script')
5: script.src = src;
6: document.body.appendChild(script);
ここも特に難しくありません。
body要素に新しいscript要素を追加することで、外部jsを読み込みます。
今回の場合、
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.2.6/jquery.min.js"></script>
という要素が追加されることになります。
いよいよメイン!7~10行目
7: setTimeout(function() {
8: if (!check()) setTimeout(arguments.callee, 100);
9: else next();
10: }, 100);
ここが一番重要&難しいところだと思います。
まず、7行目と10行目では、setTimeoutを使って、渡した無名関数を100ミリ秒後に実行する、という指定を行っています。
そして肝心なのは、その無名関数です。
8行目前半のif文では、2行目で作った関数を呼び出しています。
先ほども書きましたが、無名関数の中では宣言されていないcheckという変数を使っています。
JavaScriptでは、親関数(エンクロージャ)の中で宣言した変数(レキシカル変数)は、その中で作られた関数(クロージャ)からずっと参照することができます。(親関数の実行が終わってもなくなりません)
そのため、ここでもcheck()とすることで、指定オブジェクトの有無を確認する関数を呼び出すことができるのです。
(クロージャの実用的な使い方ですね!)
順番が前後しますが、9行目は外部jsが読み込まれた場合の処理で、実行したい関数を呼び出しています。
このnextも親関数の中で引数として宣言されているので、ここで使用することができます。
ラストスパート!!
長々と説明してきましたが、8行目後半を説明すればおしまいです。
(あとちょっと耐えてください)
ここはcheck()がfalseなら、つまり、まだ指定オブジェクトが存在しない場合に実行される処理です。
さっきと同じようにsetTimeout関数を使っているので、100ミリ秒後にarguments.calleeという怪しいものを実行しようという意味になります。
arguments.calleeは、実行中の関数自身を参照しています。
ここで注意ですが、実行中の関数自身とは、load関数ではなく7行目でsetTimeoutに渡されている無名関数のことです。
したがって、check()がtrueを返すまで、100ミリ秒毎に指定オブジェクトの有無をチェックするという処理を繰り返すことができるのです。
そして!外部jsファイルの読み込みが完了すれば、先ほど説明した9行目が実行されて、
と、表示された後、無事この関数の役目は終了となります。
[おわりに]
以上、僕はこんな風に考えてます。
JS勉強中の同士のお役に立てれば幸いです。
(理解が足りないところも多々あると思いますが…;)
突っ込みは大歓迎です!
お気づきの点があればコメント等でご指摘お願いしますm(_ _)m
[余談]
こういう処理も遅延って表現していいのかなぁ?
amachangさんの記事では動的ロードとされてるけど…。
まぁ遅延ロードって書いてる人もいる(↓)し、いいか!
なにより、遅延って言葉はハッカーっぽくてステキ!!
[See also]
JavaScriptで遅延ロードをする方法についてのおさらい (Clouder::Blogger)
malaさんの作。(オリジナルの記事はこちら)
2005年…3年前かぁ、すごいなぁ。
waitのみの実装のため関数自体が短く、あまりトリッキーな書き方もされていないので、こっちの方が直感的にわかりやすいかもです。
需要があれば解説しますよ?
クオリティはこの程度になりますが(笑)
[参考]
Core JavaScript 1.5 Reference:Functions - MDC
Core JavaScript 1.5 Guide:Working with Closures - MDC
[追記]
誤字を訂正。(2008/06/27 19:05)
まさか自分のドメイン名を間違えるとは^^;
PR