在JS内动态加载JS

467

我有一个动态网页,在一个 IF 条件下需要在另一个 JavaScript 文件中导入外部 JS 文件。

我尝试寻找可行的解决方案,但并没有成功。

我已经尝试使用 document.createElement() 将 JS 文件加载到 DOM 中,但也没有成功。显然,JS 已经被加载到了 DOM 中,但是在当前的 JS 文件中无法访问。

使用 jQuery 解决方案也可以。


你尝试通过这样做来实现什么目的(以便可以提供替代方案)? - Stephen
我正在创建一个动态页面。它可以有条件地显示一些效果。但是外部JS不能永久地包含在HTML中。 - Riju Mahna
1
参见:http://www.jspatterns.com/the-ridiculous-case-of-adding-a-script-element/ - Dave Jarvis
12个回答

919

我猜你在你的仅使用DOM的解决方案中做了类似这样的事情:

var script = document.createElement('script');
script.src = something;
//do stuff with the script

首先,那样做行不通,因为脚本没有添加到文档树中,所以它不会被加载。此外,即使你这么做了,JavaScript的执行也会继续,而其他脚本正在加载,因此在该脚本完全加载之前,它的内容不会对您可用。

您可以监听脚本的load事件,并像往常一样处理结果。所以:

var script = document.createElement('script');
script.onload = function () {
    //do stuff with the script
};
script.src = something;

document.head.appendChild(script); //or something of the likes

38
不必把事情复杂化,棒极了。 - deepelement
70
确保在onload属性之后设置src属性。来源 - woojoo666
22
这真的应该是最好的答案,对于像这样简单的任务,不需要使用jQuery! - Matthew
13
为什么要在“onload”之后?仅在“appendChild”之前设置“src”不就足够了吗? - A-Sharabiani
6
抱歉,您是正确的,仅当元素已在页面上时才重要。从我的经验来看,在设置“src”之前将元素放在页面上更为常见,这主要是因为有许多情况需要动态更改“src”,例如“img”标签等。因此,我认为最好的做法是习惯于在“onload”之后放置“src”。 - woojoo666
显示剩余8条评论

178

有时候 jQuery 的 $.getScript() 会有 bug,所以我使用自己的实现方式:

jQuery.loadScript = function (url, callback) {
    jQuery.ajax({
        url: url,
        dataType: 'script',
        success: callback,
        async: true
    });
}

并像这样使用:

if (typeof someObject == 'undefined') $.loadScript('url_to_someScript.js', function(){
    //Stuff to do after someScript has loaded
});

3
我稍微调整了你的脚本,让它能够在加载后执行,否则会先加载再执行...... jQuery.pluginSafe = function (name, url, callback) { if(jQuery[name]){ callback; } else { jQuery.ajax({...}); } } - Terry Riegel
81
你能提供一些关于什么时候出现问题的信息吗? - user151496
4
因为这个文件被 eval 运行,所以在 DevTools 的 Sources 中没有显示加载。这并不是理想的情况。 - Vanuan
3
另一个问题是这样会吞掉任何语法错误,所以你需要在fail()中捕获它们。 - Vanuan

128

我需要经常这样做,所以我使用这个:

var loadJS = function(url, implementationCode, location){
    //url is URL of external file, implementationCode is the code
    //to be called from the file, location is the location to 
    //insert the <script> element

    var scriptTag = document.createElement('script');
    scriptTag.src = url;

    scriptTag.onload = implementationCode;
    scriptTag.onreadystatechange = implementationCode;

    location.appendChild(scriptTag);
};
var yourCodeToBeCalled = function(){
//your code goes here
}
loadJS('yourcode.js', yourCodeToBeCalled, document.body);

了解更多信息,请参见此站点如何在另一个JavaScript文件中包含JavaScript文件?,这是我的函数想法的来源。


2
因为包含了 onload,所以点赞。 - henrykodev
这段代码非常出色。在chrome/firefox/opera上只需要进行非常少的修改就可以实现完全功能。我用6行html编写了自己的markdown重写器,它加载了markdown脚本。对于内容,我使用<template>标签和markdown添加。重写器将转换markdown为HTML的结果附加到其中。真的非常美丽。谢谢你。 - jlettvin
再多做一点工作,我把所有的HTML都简化成了3行。带有内容的<template>标签被转换并附加。比我以前的任何努力都要好。 - jlettvin
<!DOCTYPE html> <html lang="en"><head><meta charset="utf-8" /></head><body> <nav></nav> <template class="markdown"> == hello == </template> <template id="Spanish" class="markdown"> == hola == </template> </body><script type="text/javascript" src="/js/rewrite.js"></script> </html> - jlettvin

30

您可以在页面内部动态加载js,而不是加载另一个js文件。

您需要使用getScript来加载js文件。

$.getScript("ajax/test.js", function(data, textStatus, jqxhr) {
  console.log(data); // data returned
  console.log(textStatus); // success
  console.log(jqxhr.status); // 200
  console.log('Load was performed.');
});

这是一个优雅的解决方案,可以轻松地工作。它也很好地嵌套在其他函数中。强烈推荐此选项。 - user1239087
8
我不喜欢它使用jQuery,我需要用纯JS完成这个。 - Marc van Nieuwenhuijzen

20

死灵术。

我使用这个来加载依赖脚本;
它可以在IE8+上工作,而不需要添加任何其他库的依赖,如jQuery!

var cScriptLoader = (function ()
{
    function cScriptLoader(files)
    {
        var _this = this;
        this.log = function (t)
        {
            console.log("ScriptLoader: " + t);
        };
        this.withNoCache = function (filename)
        {
            if (filename.indexOf("?") === -1)
                filename += "?no_cache=" + new Date().getTime();
            else
                filename += "&no_cache=" + new Date().getTime();
            return filename;
        };
        this.loadStyle = function (filename)
        {
            // HTMLLinkElement
            var link = document.createElement("link");
            link.rel = "stylesheet";
            link.type = "text/css";
            link.href = _this.withNoCache(filename);
            _this.log('Loading style ' + filename);
            link.onload = function ()
            {
                _this.log('Loaded style "' + filename + '".');
            };
            link.onerror = function ()
            {
                _this.log('Error loading style "' + filename + '".');
            };
            _this.m_head.appendChild(link);
        };
        this.loadScript = function (i)
        {
            var script = document.createElement('script');
            script.type = 'text/javascript';
            script.src = _this.withNoCache(_this.m_js_files[i]);
            var loadNextScript = function ()
            {
                if (i + 1 < _this.m_js_files.length)
                {
                    _this.loadScript(i + 1);
                }
            };
            script.onload = function ()
            {
                _this.log('Loaded script "' + _this.m_js_files[i] + '".');
                loadNextScript();
            };
            script.onerror = function ()
            {
                _this.log('Error loading script "' + _this.m_js_files[i] + '".');
                loadNextScript();
            };
            _this.log('Loading script "' + _this.m_js_files[i] + '".');
            _this.m_head.appendChild(script);
        };
        this.loadFiles = function ()
        {
            // this.log(this.m_css_files);
            // this.log(this.m_js_files);
            for (var i = 0; i < _this.m_css_files.length; ++i)
                _this.loadStyle(_this.m_css_files[i]);
            _this.loadScript(0);
        };
        this.m_js_files = [];
        this.m_css_files = [];
        this.m_head = document.getElementsByTagName("head")[0];
        // this.m_head = document.head; // IE9+ only
        function endsWith(str, suffix)
        {
            if (str === null || suffix === null)
                return false;
            return str.indexOf(suffix, str.length - suffix.length) !== -1;
        }
        for (var i = 0; i < files.length; ++i)
        {
            if (endsWith(files[i], ".css"))
            {
                this.m_css_files.push(files[i]);
            }
            else if (endsWith(files[i], ".js"))
            {
                this.m_js_files.push(files[i]);
            }
            else
                this.log('Error unknown filetype "' + files[i] + '".');
        }
    }
    return cScriptLoader;
})();
var ScriptLoader = new cScriptLoader(["foo.css", "Scripts/Script4.js", "foobar.css", "Scripts/Script1.js", "Scripts/Script2.js", "Scripts/Script3.js"]);
ScriptLoader.loadFiles();

如果您对用于创建此内容的typescript版本感兴趣:

class cScriptLoader {
    private m_js_files: string[];
    private m_css_files: string[];
    private m_head:HTMLHeadElement;
    
    private log = (t:any) =>
    {
        console.log("ScriptLoader: " + t);
    }
    
    
    constructor(files: string[]) {
        this.m_js_files = [];
        this.m_css_files = [];
        this.m_head = document.getElementsByTagName("head")[0];
        // this.m_head = document.head; // IE9+ only
        
        
        function endsWith(str:string, suffix:string):boolean 
        {
            if(str === null || suffix === null)
                return false;
                
            return str.indexOf(suffix, str.length - suffix.length) !== -1;
        }
        
        
        for(let i:number = 0; i < files.length; ++i) 
        {
            if(endsWith(files[i], ".css"))
            {
                this.m_css_files.push(files[i]);
            }
            else if(endsWith(files[i], ".js"))
            {
                this.m_js_files.push(files[i]);
            }
            else
                this.log('Error unknown filetype "' + files[i] +'".');
        }
        
    }
    
    
    public withNoCache = (filename:string):string =>
    {
        if(filename.indexOf("?") === -1)
            filename += "?no_cache=" + new Date().getTime();
        else
            filename += "&no_cache=" + new Date().getTime();
            
        return filename;    
    }
    

    public loadStyle = (filename:string) =>
    {
        // HTMLLinkElement
        let link = document.createElement("link");
        link.rel = "stylesheet";
        link.type = "text/css";
        link.href = this.withNoCache(filename);
        
        this.log('Loading style ' + filename);
        link.onload = () =>
        {
            this.log('Loaded style "' + filename + '".');
            
        };
        
        link.onerror = () =>
        {
            this.log('Error loading style "' + filename + '".');
        };
        
        this.m_head.appendChild(link);
    }
    
    
    public loadScript = (i:number) => 
    {
        let script = document.createElement('script');
        script.type = 'text/javascript';
        script.src = this.withNoCache(this.m_js_files[i]);
        
        var loadNextScript = () => 
        {
            if (i + 1 < this.m_js_files.length)
            {
                this.loadScript(i + 1);
            }
        }
        
        script.onload = () =>
        {
            this.log('Loaded script "' + this.m_js_files[i] + '".');
            loadNextScript();
        };
        
        
        script.onerror = () =>
        {
            this.log('Error loading script "' + this.m_js_files[i] + '".');
            loadNextScript();
        };
        
        
        this.log('Loading script "' + this.m_js_files[i] + '".');
        this.m_head.appendChild(script);
    }
    
    public loadFiles = () => 
    {
        // this.log(this.m_css_files);
        // this.log(this.m_js_files);
        
        for(let i:number = 0; i < this.m_css_files.length; ++i)
            this.loadStyle(this.m_css_files[i])
        
        this.loadScript(0);
    }
    
}


var ScriptLoader = new cScriptLoader(["foo.css", "Scripts/Script4.js", "foobar.css", "Scripts/Script1.js", "Scripts/Script2.js", "Scripts/Script3.js"]);
ScriptLoader.loadFiles();

如果要加载动态脚本列表,可以将脚本写入属性中,例如data-main,例如: <script src="scriptloader.js" data-main="file1.js,file2.js,file3.js,etc." ></script> 然后使用element.getAttribute("data-main").split(',')
var target = document.currentScript || (function() {
  var scripts = document.getElementsByTagName('script');
  // Note: this is for IE as IE doesn't support currentScript
  // this does not work if you have deferred loading with async
  // e.g. <script src="..." async="async" ></script>
  // https://web.archive.org/web/20180618155601/https://www.w3schools.com/TAgs/att_script_async.asp
  return scripts[scripts.length - 1];
})();

target.getAttribute("data-main").split(',')

获取列表。


1
谢谢,太棒了。我认为值得添加回调选项,因为我们可能希望在资源加载结束时执行基于这些资源的函数。 - Jacob
为什么在 TypeScript 代码中要使用 var - canbax
1
@canbax:旧版本的TS没有拒绝var - 旧习惯难改。 - Stefan Steiger

10

jQuery.getScript()方法是Ajax函数的简写形式(带有dataType属性:$.ajax({ url: url,dataType: "script"}))。

如果您希望脚本可以被缓存,请使用RequireJS,或者按照jQuery对扩展jQuery.getScript方法的示例进行操作,如下所示。

jQuery.cachedScript = function( url, options ) {

  // Allow user to set any option except for dataType, cache, and url
  options = $.extend( options || {}, {
    dataType: "script",
    cache: true,
    url: url
  });

  // Use $.ajax() since it is more flexible than $.getScript
  // Return the jqXHR object so we can chain callbacks
  return jQuery.ajax( options );
};

// Usage
$.cachedScript( "ajax/test.js" ).done(function( script, textStatus ) {
  console.log( textStatus );
});

参考资料: jQuery.getScript() | jQuery API 文档


6

jQuery有$.getScript()

描述:使用GET HTTP请求从服务器加载JavaScript文件,然后执行它。


5
您可以使用JQuery来实现:
$.getScript("ajax/test.js", function(data, textStatus, jqxhr) {
  console.log(data); //data returned
  console.log(textStatus); //success
  console.log(jqxhr.status); //200
  console.log('Load was performed.');
});

这个链接应该会有帮助: http://api.jquery.com/jQuery.getScript/


5
为了编写我的插件,我需要在JS文件中加载预定义的外部脚本和样式表。为了实现这一目的,我进行了以下操作:
    this.loadRequiredFiles = function (callback) {
        var scripts = ['xx.js', 'yy.js'];
        var styles = ['zz.css'];
        var filesloaded = 0;
        var filestoload = scripts.length + styles.length;
        for (var i = 0; i < scripts.length; i++) {
            log('Loading script ' + scripts[i]);
            var script = document.createElement('script');
            script.type = 'text/javascript';
            script.src = scripts[i];
            script.onload = function () {
                log('Loaded script');
                log(this);
                filesloaded++;  // (This means increment, i.e. add one)
                finishLoad();
            };
            document.head.appendChild(script);
        }
        for (var i = 0; i < styles.length; i++) {
            log('Loading style ' + styles[i]);
            var style = document.createElement('link');
            style.rel = 'stylesheet';
            style.href = styles[i];
            style.type = 'text/css';
            style.onload = function () {
                log('Loaded style');
                log(this);
                filesloaded++;
                finishLoad();
            };
            document.head.appendChild(style);
        }
        function finishLoad() {
            if (filesloaded === filestoload) {
                callback();
            }
        }
    };

更多上下文中的脚本:

function myPlugin() {

    var opts = {
        verbose: false
    };                          ///< The options required to run this function
    var self = this;            ///< An alias to 'this' in case we're in jQuery                         ///< Constants required for this function to work

    this.getOptions = function() {
        return opts;
    };

    this.setOptions = function(options) {
        for (var x in options) {
            opts[x] = options[x];
        }
    };

    /**
     * @brief Load the required files for this plugin
     * @param {Function} callback A callback function to run when all files have been loaded
     */
    this.loadRequiredFiles = function (callback) {
        var scripts = ['xx.js', 'yy.js'];
        var styles = ['zz.css'];
        var filesloaded = 0;
        var filestoload = scripts.length + styles.length;
        for (var i = 0; i < scripts.length; i++) {
            log('Loading script ' + scripts[i]);
            var script = document.createElement('script');
            script.type = 'text/javascript';
            script.src = scripts[i];
            script.onload = function () {
                log('Loaded script');
                log(this);
                filesloaded++;
                finishLoad();
            };
            document.head.appendChild(script);
        }
        for (var i = 0; i < styles.length; i++) {
            log('Loading style ' + styles[i]);
            var style = document.createElement('link');
            style.rel = 'stylesheet';
            style.href = styles[i];
            style.type = 'text/css';
            style.onload = function () {
                log('Loaded style');
                log(this);
                filesloaded++;
                finishLoad();
            };
            document.head.appendChild(style);
        }
        function finishLoad() {
            if (filesloaded === filestoload) {
                callback();
            }
        }
    };

    /**
     * @brief Enable user-controlled logging within this function
     * @param {String} msg The message to log
     * @param {Boolean} force True to log message even if user has set logging to false
     */
    function log(msg, force) {
        if (opts.verbose || force) {
            console.log(msg);
        }
    }

    /**
     * @brief Initialise this function
     */
    this.init = function() {
        self.loadRequiredFiles(self.afterLoadRequiredFiles);
    };

    this.afterLoadRequiredFiles = function () {
        // Do stuff
    };

}

5

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接