動態加載JavaScript文件


Answers

在javascript中沒有import / include / require,但有兩種主要方法可以實現你想要的功能:

1 - 您可以使用AJAX調用加載它,然後使用eval。

這是最直接的方法,但由於Javascript的安全設置,它僅限於您的域名,並且使用eval正在為錯誤和黑客打開大門。

2 - 在HTML中添加腳本標籤和腳本URL。

絕對是最好的選擇。 您甚至可以從外部服務器加載腳本,並且在使用瀏覽器分析程序評估代碼時它很乾淨。 您可以將標籤放在網頁的頭部或身體的底部。

這兩種解決方案都在這裡討論和說明。

現在,你必須知道一個大問題。 這樣做意味著您可以遠程加載代碼。 現代Web瀏覽器將加載文件並繼續執行當前腳本,因為它們會異步加載所有內容以提高性能。

這意味著,如果直接使用這些技巧,則在您要求將其加載後,您將無法在下一行使用新加載的代碼,因為它仍會加載。

EG:my_lovely_script.js包含MySuperObject

var js = document.createElement("script");

js.type = "text/javascript";
js.src = jsFilePath;

document.body.appendChild(js);

var s = new MySuperObject();

Error : MySuperObject is undefined

然後你重新加載頁面F5。 它的工作原理! 混亂...

那麼該怎麼辦呢?

那麼,你可以使用作者在我給你的鏈接中建議的黑客行為。 總之,對於匆忙的人來說,當腳本被加載時,他使用en事件來運行回調函數。 所以你可以把所有使用遠程庫的代碼放在回調函數中。 EG:

function loadScript(url, callback)
{
    // adding the script tag to the head as suggested before
   var head = document.getElementsByTagName('head')[0];
   var script = document.createElement('script');
   script.type = 'text/javascript';
   script.src = url;

   // then bind the event to the callback function 
   // there are several events for cross browser compatibility
   script.onreadystatechange = callback;
   script.onload = callback;

   // fire the loading
   head.appendChild(script);
}

然後你編寫你想在腳本加載到lambda函數後使用的代碼:

var myPrettyCode = function() {
    // here, do what ever you want
};

然後你運行所有這些:

loadScript("my_lovely_script.js", myPrettyCode);

好,我知道了。 但編寫所有這些東西是一件痛苦的事情。

那麼,在這種情況下,你可以像往常一樣使用免費的jQuery框架,它可以讓你在一行中完成同樣的事情:

$.getScript("my_lovely_script.js", function() {
    alert("Script loaded and executed.");
    // here you can use anything you defined in the loaded script
});
Question

你如何可靠和動態地加載JavaScript文件? 這可以用來實現一個模塊或組件,當“初始化”組件時,會根據需要動態加載所有需要的JavaScript庫腳本。

使用該組件的客戶端不需要加載實現此組件的所有庫腳本文件(並手動將<script>標記插入到其網頁中) - 只是“主要”組件腳本文件。

主流JavaScript庫如何完成這個(Prototype,jQuery等)? 這些工具是否將多個JavaScript文件合併到腳本文件的單個可再發行版本“構建版本”中? 或者他們是否動態加載輔助“圖書館”腳本?

除了這個問題: 在加載動態包含的JavaScript文件後,是否有辦法處理事件? Prototype具有document.observe用於文檔範圍的事件。 例:

document.observe("dom:loaded", function() {
  // initially hide all containers for tab content
  $$('div.tabcontent').invoke('hide');
});

腳本元素的可用事件是什麼?




jquery用它的.append()函數解決了這個問題 - 用它來加載完整的jquery ui包

/*
 * FILENAME : project.library.js
 * USAGE    : loads any javascript library
 */
    var dirPath = "../js/";
    var library = ["functions.js","swfobject.js","jquery.jeditable.mini.js","jquery-ui-1.8.8.custom.min.js","ui/jquery.ui.core.min.js","ui/jquery.ui.widget.min.js","ui/jquery.ui.position.min.js","ui/jquery.ui.button.min.js","ui/jquery.ui.mouse.min.js","ui/jquery.ui.dialog.min.js","ui/jquery.effects.core.min.js","ui/jquery.effects.blind.min.js","ui/jquery.effects.fade.min.js","ui/jquery.effects.slide.min.js","ui/jquery.effects.transfer.min.js"];

    for(var script in library){
        $('head').append('<script type="text/javascript" src="' + dirPath + library[script] + '"></script>');
    }

使用 - 在導入jquery.js後,在你的html / php / etc的頭部,你只需要包含這個文件就可以加載整個庫,並將它附加到頭部。

<script type="text/javascript" src="project.library.js"></script>



以下是我找到的一些示例代碼...有沒有人有更好的方法?

  function include(url)
  {
    var s = document.createElement("script");
    s.setAttribute("type", "text/javascript");
    s.setAttribute("src", url);
    var nodes = document.getElementsByTagName("*");
    var node = nodes[nodes.length -1].parentNode;
    node.appendChild(s);
  }



我做了和亞當基本相同的事情,但是通過修改幻燈片以確保我追加到頭標以完成工作。 我只是創建了一個包含函數(下面的代碼)來處理腳本和css文件。

此功能還會檢查以確保腳本或CSS文件尚未動態加載。 它不檢查手動編碼的值,並且可能有更好的方法來做到這一點,但它有助於達到目的。

function include( url, type ){
    // First make sure it hasn't been loaded by something else.
    if( Array.contains( includedFile, url ) )
        return;

    // Determine the MIME-type
    var jsExpr = new RegExp( "js$", "i" );
    var cssExpr = new RegExp( "css$", "i" );
    if( type == null )
        if( jsExpr.test( url ) )
            type = 'text/javascript';
        else if( cssExpr.test( url ) )
            type = 'text/css';

    // Create the appropriate element.
    var tag = null;
    switch( type ){
        case 'text/javascript' :
            tag = document.createElement( 'script' );
            tag.type = type;
            tag.src = url;
            break;
        case 'text/css' :
            tag = document.createElement( 'link' );
            tag.rel = 'stylesheet';
            tag.type = type;
            tag.href = url;
            break;
    }

    // Insert it to the <head> and the array to ensure it is not
    // loaded again.
    document.getElementsByTagName("head")[0].appendChild( tag );
    Array.add( includedFile, url );
}



我知道我對這個問題的答案有點遲,但是,這裡是www.html5rocks.com上的一篇很棒的文章 - 深入了解腳本加載的晦澀難懂的水域

在那篇文章中得出的結論是,在瀏覽器支持方面,動態加載JavaScript文件而不阻止內容呈現的最佳方式如下:

考慮到你有四個腳本命名為script1.js, script2.js, script3.js, script4.js那麼你可以通過應用async = false來實現

[
  'script1.js',
  'script2.js',
  'script3.js',
  'script4.js'
].forEach(function(src) {
  var script = document.createElement('script');
  script.src = src;
  script.async = false;
  document.head.appendChild(script);
});

現在, Spec說 :一起下載,盡快下載完成。

Firefox <3.6,Opera說:我不知道這個“異步”是什麼,但是我只是按照它們添加的順序執行通過JS添加的腳本。

Safari 5.0說:我理解“異步”,但不明白將其設置為“假”與JS。 我會盡快按照任何順序執行腳本。

IE <10表示:不知道“異步”,但有一個解決方法使用“onreadystatechange”。

其他的一切都是這樣說的:我是你的朋友,我們將在書中這樣做。

現在,完整的代碼與IE <10解決方法:

var scripts = [
  'script1.js',
  'script2.js',
  'script3.js',
  'script4.js'
];
var src;
var script;
var pendingScripts = [];
var firstScript = document.scripts[0];

// Watch scripts load in IE
function stateChange() {
  // Execute as many scripts in order as we can
  var pendingScript;
  while (pendingScripts[0] && pendingScripts[0].readyState == 'loaded') {
    pendingScript = pendingScripts.shift();
    // avoid future loading events from this script (eg, if src changes)
    pendingScript.onreadystatechange = null;
    // can't just appendChild, old IE bug if element isn't closed
    firstScript.parentNode.insertBefore(pendingScript, firstScript);
  }
}

// loop through our script urls
while (src = scripts.shift()) {
  if ('async' in firstScript) { // modern browsers
    script = document.createElement('script');
    script.async = false;
    script.src = src;
    document.head.appendChild(script);
  }
  else if (firstScript.readyState) { // IE<10
    // create a script and add it to our todo pile
    script = document.createElement('script');
    pendingScripts.push(script);
    // listen for state changes
    script.onreadystatechange = stateChange;
    // must set src AFTER adding onreadystatechange listener
    // else we’ll miss the loaded event for cached scripts
    script.src = src;
  }
  else { // fall back to defer
    document.write('<script src="' + src + '" defer></'+'script>');
  }
}

稍後的一些技巧和縮小,它是362字節

!function(e,t,r){function n(){for(;d[0]&&"loaded"==d[0][f];)c=d.shift(),c[o]=!i.parentNode.insertBefore(c,i)}for(var s,a,c,d=[],i=e.scripts[0],o="onreadystatechange",f="readyState";s=r.shift();)a=e.createElement(t),"async"in i?(a.async=!1,e.head.appendChild(a)):i[f]?(d.push(a),a[o]=n):e.write("<"+t+' src="'+s+'" defer></'+t+">"),a.src=s}(document,"script",[
  "//other-domain.com/1.js",
  "2.js"
])



所有主要的JavaScript庫,如jscript,原型,YUI都支持加載腳本文件。 例如,在YUI中,加載核心後,您可以執行以下操作來加載日曆控件

var loader = new YAHOO.util.YUILoader({

    require: ['calendar'], // what components?

    base: '../../build/',//where do they live?

    //filter: "DEBUG",  //use debug versions (or apply some
                        //some other filter?

    //loadOptional: true, //load all optional dependencies?

    //onSuccess is the function that YUI Loader
    //should call when all components are successfully loaded.
    onSuccess: function() {
        //Once the YUI Calendar Control and dependencies are on
        //the page, we'll verify that our target container is 
        //available in the DOM and then instantiate a default
        //calendar into it:
        YAHOO.util.Event.onAvailable("calendar_container", function() {
            var myCal = new YAHOO.widget.Calendar("mycal_id", "calendar_container");
            myCal.render();
        })
     },

    // should a failure occur, the onFailure function will be executed
    onFailure: function(o) {
        alert("error: " + YAHOO.lang.dump(o));
    }

 });

// Calculate the dependency and insert the required scripts and css resources
// into the document
loader.insert();



剛剛發現YUI 3中的一個很棒的功能(在預覽版發布的時候)。 您可以輕鬆地將依賴關係插入YUI庫和“外部”模塊(您正在查找的內容),而無需太多代碼: YUI Loader

它還解答了關於加載外部模塊後立即調用函數的第二個問題。

例:

YUI({
    modules: {
        'simple': {
            fullpath: "http://example.com/public/js/simple.js"
        },
        'complicated': {
            fullpath: "http://example.com/public/js/complicated.js"
            requires: ['simple']  // <-- dependency to 'simple' module
        }
    },
    timeout: 10000
}).use('complicated', function(Y, result) {
    // called as soon as 'complicated' is loaded
    if (!result.success) {
        // loading failed, or timeout
        handleError(result.msg);
    } else {
        // call a function that needs 'complicated'
        doSomethingComplicated(...);
    }
});

為我完美工作,並具有管理依賴關係的優勢。 有關YUI 2日曆示例,請參閱YUI文檔。




我編寫了一個簡單的模塊,用JavaScript自動化導入/包括模塊腳本的工作。 試試吧,請留下一些反饋! :)有關代碼的詳細說明,請參閱此博客文章: http://stamat.wordpress.com/2013/04/12/javascript-require-import-include-modules/ : http://stamat.wordpress.com/2013/04/12/javascript-require-import-include-modules/

var _rmod = _rmod || {}; //require module namespace
_rmod.on_ready_fn_stack = [];
_rmod.libpath = '';
_rmod.imported = {};
_rmod.loading = {
    scripts: {},
    length: 0
};

_rmod.findScriptPath = function(script_name) {
    var script_elems = document.getElementsByTagName('script');
    for (var i = 0; i < script_elems.length; i++) {
        if (script_elems[i].src.endsWith(script_name)) {
            var href = window.location.href;
            href = href.substring(0, href.lastIndexOf('/'));
            var url = script_elems[i].src.substring(0, script_elems[i].length - script_name.length);
            return url.substring(href.length+1, url.length);
        }
    }
    return '';
};

_rmod.libpath = _rmod.findScriptPath('script.js'); //Path of your main script used to mark the root directory of your library, any library


_rmod.injectScript = function(script_name, uri, callback, prepare) {

    if(!prepare)
        prepare(script_name, uri);

    var script_elem = document.createElement('script');
    script_elem.type = 'text/javascript';
    script_elem.title = script_name;
    script_elem.src = uri;
    script_elem.async = true;
    script_elem.defer = false;

    if(!callback)
        script_elem.onload = function() {
            callback(script_name, uri);
        };

    document.getElementsByTagName('head')[0].appendChild(script_elem);
};

_rmod.requirePrepare = function(script_name, uri) {
    _rmod.loading.scripts[script_name] = uri;
    _rmod.loading.length++;
};

_rmod.requireCallback = function(script_name, uri) {
    _rmod.loading.length--;
    delete _rmod.loading.scripts[script_name];
    _rmod.imported[script_name] = uri;

    if(_rmod.loading.length == 0)
        _rmod.onReady();
};

_rmod.onReady = function() {
    if (!_rmod.LOADED) {
        for (var i = 0; i < _rmod.on_ready_fn_stack.length; i++){
            _rmod.on_ready_fn_stack[i]();
        });
        _rmod.LOADED = true;
    }
};

//you can rename based on your liking. I chose require, but it can be called include or anything else that is easy for you to remember or write, except import because it is reserved for future use.
var require = function(script_name) {
    var np = script_name.split('.');
    if (np[np.length-1] === '*') {
        np.pop();
        np.push('_all');
    }

    script_name = np.join('.');
    var uri = _rmod.libpath + np.join('/')+'.js';
    if (!_rmod.loading.scripts.hasOwnProperty(script_name) 
     && !_rmod.imported.hasOwnProperty(script_name)) {
        _rmod.injectScript(script_name, uri, 
            _rmod.requireCallback, 
                _rmod.requirePrepare);
    }
};

var ready = function(fn) {
    _rmod.on_ready_fn_stack.push(fn);
};

// ----- USAGE -----

require('ivar.util.array');
require('ivar.util.string');
require('ivar.net.*');

ready(function(){
    //do something when required scripts are loaded
});



保持它的好,簡短,可維護! :]

// 3rd party plugins / script (don't forget the full path is necessary)
var FULL_PATH = '', s =
[
    FULL_PATH + 'plugins/script.js'      // Script example
    FULL_PATH + 'plugins/jquery.1.2.js', // jQuery Library 
    FULL_PATH + 'plugins/crypto-js/hmac-sha1.js',      // CryptoJS
    FULL_PATH + 'plugins/crypto-js/enc-base64-min.js'  // CryptoJS
];

function load(url)
{
    var ajax = new XMLHttpRequest();
    ajax.open('GET', url, false);
    ajax.onreadystatechange = function ()
    {
        var script = ajax.response || ajax.responseText;
        if (ajax.readyState === 4)
        {
            switch(ajax.status)
            {
                case 200:
                    eval.apply( window, [script] );
                    console.log("library loaded: ", url);
                    break;
                default:
                    console.log("ERROR: library not loaded: ", url);
            }
        }
    };
    ajax.send(null);
}

 // initialize a single load 
load('plugins/script.js');

// initialize a full load of scripts
if (s.length > 0)
{
    for (i = 0; i < s.length; i++)
    {
        load(s[i]);
    }
}

此代碼只是一個簡短的功能示例, 可能需要額外的功能才能在任何(或給定)平台上提供全面支持。




這裡是一個加載JS文件的函數的簡單例子。 相關要點:

  • 你不需要jQuery,所以你可以最初使用它來加載jQuery.js文件
  • 它與回調是異步的
  • 它可以確保只加載一次,因為它可以保留帶有加載URL的記錄,從而避免使用網絡
  • 與jQuery $.ajax$.getScript相反,您可以使用隨機數,從而解決CSP unsafe-inline問題。 只需使用屬性script.nonce
var getScriptOnce = function() {

    var scriptArray = []; //array of urls (closure)

    //function to defer loading of script
    return function (url, callback){
        //the array doesn't have such url
        if (scriptArray.indexOf(url) === -1){

            var script=document.createElement('script');
            script.src=url;
            var head=document.getElementsByTagName('head')[0],
                done=false;

            script.onload=script.onreadystatechange = function(){
                if ( !done && (!this.readyState || this.readyState == 'loaded' || this.readyState == 'complete') ) {
                    done=true;
                    if (typeof callback === 'function') {
                        callback();
                    }
                    script.onload = script.onreadystatechange = null;
                    head.removeChild(script);

                    scriptArray.push(url);
                }
            };

            head.appendChild(script);
        }
    };
}();

現在你簡單地使用它

getScriptOnce("url_of_your_JS_file.js");



有沒有人有更好的方法?

我認為只要將腳本添加到頁面的最後一個節點,將腳本添加到主體會更容易。 這個怎麼樣:

function include(url) {
  var s = document.createElement("script");
  s.setAttribute("type", "text/javascript");
  s.setAttribute("src", url);
  document.body.appendChild(s);
}