<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>仮ぶろぐ &#187; 遅延ロード</title>
	<atom:link href="http://plusb.jp/blog/?feed=rss2&#038;tag=%E9%81%85%E5%BB%B6%E3%83%AD%E3%83%BC%E3%83%89" rel="self" type="application/rss+xml" />
	<link>http://plusb.jp/blog</link>
	<description>今更なことをそれでもつらづらと書くブログ</description>
	<lastBuildDate>Tue, 07 May 2013 09:06:48 +0000</lastBuildDate>
	<language>ja</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.5.1</generator>
		<item>
		<title>JavaScriptの遅延ロード</title>
		<link>http://plusb.jp/blog/?p=758</link>
		<comments>http://plusb.jp/blog/?p=758#comments</comments>
		<pubDate>Fri, 05 Aug 2011 06:21:40 +0000</pubDate>
		<dc:creator>M. K.</dc:creator>
				<category><![CDATA[dq]]></category>
		<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[tips]]></category>
		<category><![CDATA[プログラミング]]></category>
		<category><![CDATA[遅延ロード]]></category>

		<guid isPermaLink="false">http://plusb.jp/blog/?p=758</guid>
		<description><![CDATA[久しぶりに今更なネタですが、JavaScriptの遅延ロードについてです。 単純にJavaScriptのコードで動的にスクリプトを読み込みたいだけならオンデマンドにScriptタグをDOMに追加するだけですみます。 とこ &#8230; <a href="http://plusb.jp/blog/?p=758">続きを読む <span class="meta-nav">&#8594;</span></a>]]></description>
				<content:encoded><![CDATA[<p>久しぶりに今更なネタですが、JavaScriptの遅延ロードについてです。<br />
単純にJavaScriptのコードで動的にスクリプトを読み込みたいだけならオンデマンドに<code>Script</code>タグをDOMに追加するだけですみます。<br />
ところが、遅延ロードありきでライブラリやらを構築し出すと、読み込み完了している必要があるだとか依存関係だとかの問題が発生するわけです。<br />
<span id="more-758"></span><br />
これから説明するコードは拙作のライブラリ(<a href="http://plusb.js/dqfw/js/dq.js">dq.js</a>)から抜粋しています。</p>
<h3>とりあえず遅延ロード</h3>
<p>スクリプト内から動的にスクリプトをロードする<code>include()</code>関数の例です。</p>
<pre>
    include = function (file) {
        var s = document.createElement('script');
        s.src = file;
        s.type = 'text/javascript';
        s.defer = 'defer';
        document.head.appendChild(s);
    }
</pre>
<p>このコード自体はなんでも無いですね。<code>script</code>タグをheadタグに追加しているだけです。</p>
<h3>ロード完了を待つ</h3>
<p>そして読み込み完了を待って、任意のコードを実行する<code>lazyLoad()</code>を実装します。</p>
<pre>
    lazyLoad = function(url, ctl, fn) {
        function _check(ctl) { <span style="color:Red;">//オブジェクトの有無でロード完了を判定</span>
            return !!(eval(ctl));
        }
        if (_check(ctl)) {
            fn &#038;&#038; fn(); <span style="color:Red;">//ロード済みなら指定の関数をすぐ実行</span>
        } else {
            include(url);
            setTimeout(function () {
                if (!_check(ctl)) {
                    setTimeout(arguments.callee, 15.625);
                } else {
                    fn &#038;&#038; fn();<span style="color:Red;">//読み込みが終わったら関数を実行</span>
                }
            }, 15.625);
        }
    }
</pre>
<p>今でもそうだと思うのですが、scriptタグの読み込み完了イベントは拾えないので代わりに遅延ロードしたスクリプトを実行した結果として作成されるオブジェクトの有無を判定に利用します。<br />
後は既に読み込み済みなら遅延ロードを実行しないようにチェックを挟んでおきます。</p>
<h3>複数箇所からの呼び出し</h3>
<p>先のコードではとりあえず読み込み済みのチェックは実施していますが、遅延ロードが完了する前に複数回同じスクリプトが呼び出されると無駄があったり面倒くさいことが発生するかもしれません。<br />
そこで同じスクリプトに対しては一度しかinclude()が呼び出されないようにします。</p>
<pre>
    _urls = [];
    lazyLoad = function (url, ctl, fn) {
        function _check(ctl) {
            return !!(eval(ctl));
        }
        if (_check(ctl)) {
            fn &#038;&#038; fn();
            _remove$lazyLoad();
        } else {<span style="color: Red;">
            if (_urls[url]) { <span style="color: Red;">//リクエスト済みならfnのみ登録して終了。</span>
                _urls[url].push(fn);
                return;
            }
            _urls[url] = [];</span>
            include(url);<span style="color: Red;">
            _urls[url].push(fn);</span>
            setTimeout(function () {
                if (!_check(ctl)) {
                    setTimeout(arguments.callee, 15.625);
                } else {<span style="color: Red;">
                    for (var i = 0; i < DQ._urls[url].length; i++) {
                        _urls[url][i] &#038;&#038; DQ._urls[url][i].call();
                    }
                    delete _urls[url];</span>
                }
            }, 15.625);
        }
    }
</pre>
<p>読み込み中のスクリプトと読み込み完了時に実行される関数を<code>_urls</code>に追加しています。<br />
`</p>
<h3>複数のスクリプトを遅延ロードする</h3>
<p>複数のスクリプトを遅延ロードしてから自身のコードを実行したい場合どうなるのでしょうか。</p>
<pre>
lazyLoad("first.js", "Window.FIRST", function() {
  layzLoad("second.js", "Window.SECOND", function() {
      ... //自分のコード
  });
});
</pre>
<p>もしくは</p>
<pre>
lazyLoad("first.js", "Window.FIRST");
layzLoad("second.js", "Window.SECOND", function() {
    ... //自分のコード
});
</pre>
<p>まあ、どちらもいまいちですね。<br />
そこで、全ての遅延ロードが完了したら呼び出される、<code>afterLoad()</code>という関数を用意します。</p>
<pre>
    lazyLoad = function (url, ctl, fn) {
        function _check(ctl) {
            return !!(eval(ctl));
        }
        if (_check(ctl)) {
            fn &#038;&#038; fn();
            <span style="color:red;">__remove$lazyLoad();</span>
        } else {
            if (_urls[url]) {
                _urls[url].push(fn);
                return;
            }
            _urls[url] = [];
            include(url);
            _urls[url].push(fn);

            setTimeout(function () {
                if (!_check(ctl)) {
                    setTimeout(arguments.callee, 15.625);
                } else {
                    for (var i = 0; i < _urls[url].length; i++) {
                        _urls[url][i] &#038;&#038; _urls[url][i].call();
                    }
                    delete _urls[url];
                    <span style="color:red;">_remove$lazyLoad();</span>
                }

            }, 15.625);
        }
<span style="color:red;">_
        function _remove$lazyLoad() {
            var cnt = 0;
            for (var nm in DQ._urls) {
                cnt += _urls.hasOwnProperty(nm) ? 1 : 0;
            }
            if (cnt == 0) {
                __trigger &#038;&#038; setTimeout(DQ.__trigger, 0);
            }
        }
    }

__trigger = function () {
    for (var i = 0; i < _onLoad.length; i++) {
        _onLoad[i].call();
    }
    _onLoad.length = 0;
    delete __trigger;
}
_onLoad = [];
afterLoad = function (callback) {
    _onLoad.push(callback);
}</span>
</pre>
<p>使い方は以下の通りです。</p>
<pre>
lazyLoad("first.js", "Window.FIRST");
layzLoad("second.js", "Window.SECOND");
afterLoad(function() {
    ... //自分のコード
});
</pre>
<p>より複雑なケースでは問題が出る可能性もありますが、そこは宿題ということで。</p>
]]></content:encoded>
			<wfw:commentRss>http://plusb.jp/blog/?feed=rss2&#038;p=758</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
