動的制御

【JavaScript】 スクリプトファイルを任意の順番で動的に読み込む方法

更新日:2020/02/05

JavaScriptファイルを順番に読み込み、読み込めないときはエラーを出力してみる。

 

動的にJSファイルを読み込む

ようするに、scriptタグを動的に作成すればいいらしい。

また、作成してbodyに追加する前にonloadなどのイベントを設定しておくと、読み込み結果を取得できる。

イベント内容備考
onload読み込み完了後に呼び出される
onreadystatechange読み込み状況をモニターするIE11以降はサポートされない?
onerror読み込みエラー時に発呼び出される

onreadystatechangeは、作成したDOM要素のreadyStateで状況確認する。

readyState内容備考
loading読み込み中
interactive読み込み・解析完了htmlドキュメントなどの場合は、画像・スクリプト読み込み中
loaded読み込み・解析完了?古いsafariはinteractiveではなくloadedらしい?
complete全て読み込み完了

onloadとonreadystatechangeは、ブラウザによってサポートされていたりサポートされていなかったりと混とんとしていて、正直よくわからない。
とりあえず、なんでもOKなようにプログラムを作成してみます。

JavaScript:動的にスクリプトを読み込む


const LoadFile = ( filename , callback ) => {
        let ended = false;

    // scriptタグ作成
        let elm = document.createElement('script');
        elm.src = filename;
    // イベントハンドラ設定
        elm.onload = function(){
            if( !ended ) {
                ended=true;
                elm = elm.onload = elm.onreadystatechange = elm.onerror = null;
                callback( "ok" );
            }
        };
        elm.onreadystatechange = function () {
            if( !ended && ( 
            elm.readyState === "loaded" 
            || elm.readyState === "interactive" 
            || elm.readyState === "complete") ){

                ended = true;
                elm = elm.onload = elm..onreadystatechange = elm.onerror = null;
                callback( "ok" );
            }
        };
        elm.onerror = function () {
            if( !ended ){
                ended = true;
                elm = elm.onload = elm.onreadystatechange = elm.onerror = null;
                callback( "error" );
            }
        };
    document.body.appendChild( elm );
}

結果を取得したらフラグをたてて以降のイベントは無視、と同時にイベントハンドラもクリアしています。
古いブラウザを切り捨てるなら、onreadystatechange はいらないかもしれません。

JavaScript:実行結果


LoadFile( "xxx.js" , (e) => console.log(e) );
// 結果: ok または error

 

任意の順番で動的にJSファイルを読み込む

任意の順番で動的にJSファイルを読み込むには、前項のLoadFile関数を順番に実行するだけです。

JavaScript : script-load.js


const script_load = function( files , callback = null ){
    if( !(this instanceof script_load)){
        return new script_load( files , callback );
    }
    this.files = files; // 配列かどうかのチェック省略
    this.position = -1;
    this.callback = ( callback && typeof callback === 'function' ) ? callback : null;
    this.oncall = this._oncall.bind( this );
};
script_load.prototype={
    start : function(){
        if( this.position === -1 ) this._callback( "start"  );
        this.position ++;

        if( this.files.length position ){
            this._callback( "end");
            return;
        }
        this._LoadFile( this.files[ this.position ] , this.position , this.oncall )
    },
    _oncall : function( result , pos ){
        if( pos position) return;
        if( result === "ok" ) {
            this._callback( "fileok" ,  this.files[ pos ] );
            this.start();
        }
        else{
            this._callback( "filefaild" ,  this.files[ pos ] );
            this._callback( "faildEnd" );
        }
    },
    _callback : function ( status , file === null ) {
        if( this.callback === null ) return;
        this.callback( { "status" : status , "file" : file } );
    },
    _LoadFile : function( filename , position , callback ) {
        let ended = false;
        let elm = document.createElement( "script" );
        elm.src = filename;
        elm.onload = function(){
            if( !ended ) {
                ended = true;
                elm = elm.onload = elm.onreadystatechange = elm.onerror = null;
                callback( "ok" , position );
            }
        };
        elm.onreadystatechange = function () {
            if( !ended
                 && ( elm.readyState === "loaded"
                 &&  || elm.readyState === "interactive"
                 &&  || elm.readyState === "complete" ) ){
                ended = true;
                elm = elm.onload = elm.onreadystatechange = elm.onerror = null;
                callback( "ok" ,position);
            }
        };
        elm.onerror = function () {
            if( !ended ){
                ended = true;
                elm = elm.onload = elm.onreadystatechange = elm.onerror = null;
                callback( "error" , position );
            }
        };
        document.body.appendChild( elm );
    }
};

流れとしては、

(1) スクリプトファイル名の配列とコールバック関数を指定して、script_loadインスタンス作成

(2) インスタンスから、start()を呼び出す

(3) 最初のファイルとスクリプト読み込み結果を得る内部的なコールバック関数を、_LoadFile()に渡す

(4) _LoadFile()がbodyにスクリプトタグ追加

(5) 読み込み結果を内部的なコールバック関数で通知

(6) 読み込みできていたら、start()を呼び出し次のファイルを処理

ファイル名のループと、スクリプトファイルの読み込みを隔離したいけれど、オブジェクトを分けたくなくてこんな形になってます。

次に確認用にhtmlページを作成します。

script-load-test.html


<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="./script-load.js" type="text/javascript"></script>
    <script>
        ( function() {
            const tm =()=>{
                let d = new Date();
                return  d.getHours().toString() + ':'
                    + d.getMinutes().toString() + ':'
                    + d.getSeconds().toString() + ':'
                    + d.getMilliseconds().toString();
            };
 
            window.addEventListener( "DOMContentLoaded" , () => {
                
                script_load(
                    [ "./script-load-test1.js"
                    ,"./script-load-test2.js"
                    ,"./script-load-test3.js"]
                    ,( e )=>{
                        switch ( e[ "status" ])  {
                            case "start":
                                console.log( "Script Start:" + tm() );break;
                            case "end":
                                console.log( "Script End:" + tm() );break;
                            case "faildEnd":
                                console.log( "Script Load Failed:" + tm() );break;
                            case "fileok":
                                console.log( "load ok:" + e[ "file" ] + " " + tm() );break;
                            case "filefaild":
                                console.log( "load faild:"  + e[ "file" ] + " " + tm() );break;
                        }
                    }).start();
            });
        } )();
    </script>
</head>
<body>
</body>
</html>

scriptタグでscript-load.jsを指定した後、script_loadを実行しています。

次に、読み込まれるテスト用jsを作成します。
script-load-test1.js、script-load-test2.js、script-load-test3.jsの3つのファイルを読み込んでいますが、内容は全部同じです。

テスト用js


(function () {
    let current = (function() {
        if (document.currentScript) {
            return document.currentScript.src;
        } else {
            let scripts = document.getElementsByTagName('script'),
                script = scripts[scripts.length-1];
            if (script.src) {
                return script.src;
            }
        }
    })();
    let d = new Date();
    let tim = d.getHours().toString() + ':'
        + d.getMinutes().toString() + ':'
        + d.getSeconds().toString() + ':'
        + d.getMilliseconds().toString();
    console.log(tim + ' ' + current );
})();

currentは、自身のスクリプト名を取得しています。
出典はこちら

実行結果:Firefoxコンソール


Script Start:19:39:6:94
19:39:6:434 https://xxx.com/script-load-test1.js
load ok:./script-load-test1.js 19:39:6:435
19:39:6:590 https://xxx.com/script-load-test2.js
load ok:./script-load-test2.js 19:39:6:591
19:39:6:711 https://xxx.com/script-load-test3.js
load ok:./script-load-test3.js 19:39:6:712
Script End:19:39:6:712

スクリプトファイルが順番に読み込まれています。

次に存在しないファイルscript-load-test4.jsを2番目に読み込んでみます。

実行結果:Firefoxコンソール


ScriptStart:19:43:38:870
19:43:39:87https://xxx.com/script-load-test1.js
loadok:./script-load-test1.js19:43:39:87
“https://xxx.com/script-load-test4.js”&nbsp;からのスクリプトが読み込まれました。しかし、この&nbsp;MIME&nbsp;タイプ&nbsp;(“text/html”)は正しいJavaScriptのMIMEタイプではありません。
load&nbsp;faild:./script-load-test4.js
ScriptLoadFailed:19:43:39:224
<script>のソース“https://xxx.com/script-load-test4.js”の読み込みに失敗しました。

色のついている行はFirefoxからのエラー出力です。

スクリプト上からも、読み込み失敗を把握できていますね。

更新日:2020/02/05

書いた人(管理人):けーちゃん

スポンサーリンク

記事の内容について

null

こんにちはけーちゃんです。
説明するのって難しいですね。

「なんか言ってることおかしくない?」
たぶん、こんなご意見あると思います。

裏付けを取りながら記事を作成していますが、僕の勘違いだったり、そもそも情報源の内容が間違えていたりで、正確でないことが多いと思います。
そんなときは、ご意見もらえたら嬉しいです。

掲載コードについては事前に動作確認をしていますが、貼り付け後に体裁を整えるなどをした結果動作しないものになっていることがあります。
生暖かい視線でスルーするか、ご指摘ください。

ご意見、ご指摘はこちら。
https://note.affi-sapo-sv.com/info.php

 

このサイトは、リンクフリーです。大歓迎です。