【问题标题】:Dynamically Include JavaScript and Wait动态包含 JavaScript 并等待
【发布时间】:2011-04-26 17:27:15
【问题描述】:

我正在尝试做的是从 Javascript 中动态包含一个或多个 js 文件。我知道有很多技术可以解决这个问题,但我正在寻找遵循以下规则的解决方案:

1) 不使用任何 JS 框架或库(jQuery、Prototype 等)。

2) 脚本应该停止执行,直到外部 js 文件被浏览器完全加载和解析。

#2 背后的原因是外部 js 文件将包含需要在包含脚本后立即使用的函数定义。在我开始调用这些函数之前,大部分时间浏览器都没有时间解析 js 文件。

我不介意使用回调来知道脚本何时完成加载,但是:

1) 我不知道要动态包含多少脚本,所以我不想也不能写一堆嵌套的回调。我只需要知道它们何时完成加载。

2) 我的措辞是,如果浏览器缓存了 JavaScript,那么尝试使用某种“加载”事件来触发回调可能不起作用。

编辑我应该从一开始就提到这是针对插件系统的,但我想让我的问题/答案足够通用,以便对其他人有所帮助。

用户可以定义他们想要在数组中加载哪些插件。

plugins = [ 'FooPlugin', 'BarPlugin' ];

然后我会遍历数组,并为每个插件加载 js 脚本:

for(var i = 0; i < plugins.length; i++) {
    loadScript('plugins/' + plugins[i] + '.js');
}

每个插件都将自己推送到 loaded_plugins 数组(这是 FooPlugin.js 的示例)

load_plugins.push({
    name: 'FooPlugin',
    // Other plugin methods and properties here
});

【问题讨论】:

  • 不确定这个答案,所以只是将其作为评论发布。您是否尝试过使用 AJAX 将 javascript 的内容加载到客户端变量中,然后使用 eval()?由于您必须等待 AJAX 返回 200/OK 消息,因此您知道整个脚本已加载,eval() 在完成整个脚本运行之前不应将您踢到下一行。
  • 你能确定页面加载过程中需要哪些脚本吗?如果是这样,那么@Paul 的解决方案是理想的。否则,您的解决方案将取决于您想要支持的浏览器以及脚本是否与您的页面位于同一域中。
  • @steven_desu - 加载脚本中的函数/变量不会最终出现在调用 eval() 的上下文中吗?这意味着它们在使用 eval 的函数之外不可用。

标签: javascript ajax


【解决方案1】:

我知道的最简单(也是最可靠)的解决方案是简单地使用 document.write 将新的脚本标签插入到文档中(当然,也可以根据需要插入):

<script type="text/javascript">document.write('<' + 'script src="/assets/js/file.js" type="text/javascript"><\/script>');</script>

浏览器将(afaik)按照脚本标签在文档中指定的顺序解析和执行脚本,以这种方式添加时也是如此。因此,如果您将要执行的其余代码放在一个新的、单独的脚本标记中,您应该会很好。

编辑:这是一个(很多)more elaborate approach ,其基础是使用“elem = document.createElement('script')”,然后听 elem.onreadystatechange(对于 IE ) 和 elem.onload。将并行加载一整批脚本,然后在它们都在那里时触发回调。过去曾使用过此功能,当时浏览器仍主要一次加载一个脚本以加快加载速度(它基于某处具有类似目的的脚本)。用法示例:

Loader.script( [
    './js/jquery/jquery.jmap.js',
    './js/jquery/jquery.getUrlParam.js',
    './js/jquery/jquery.form.js',
    './js/jquery/jquery.gettext.js',
    './js/jquery/jquery.scrollTo.js',
    './js/jquery/jquery.prettydate.js'
  ], { complete: function() {
    // do your thing
  }
})

【讨论】:

  • document.write 只是在初始页面加载期间可行的解决方案。之后任何时候使用它都会覆盖整个页面。问题比这要复杂一些。 javascriptkit.com/javatutors/loadjavascriptcss.shtml 解释了动态加载 javascript,但这还不够聪明,甚至无法意识到脚本何时执行完毕。
  • 是的,很好;很久以前,我只直接在头部使用过这个。
  • 第二种方式是跨浏览器兼容吗?
  • 是的,是的。这也是一个很好的模块化方法。
【解决方案2】:

我想到了一些可能的方法。我要写的是步骤,而不是代码。

选项 1

  1. 通过将 JS 文件放置在带有标签的元素中来加载它。
  2. 在触发 setTimout() 后立即检查您加载的 JS 文件中的变量。
  3. 重复 #2 直到变量存在或达到 maxCounter (maxCounter = 10),这样您就不会永远坐在那里。
  4. 一旦变量存在,继续下一个 JS 文件并再次从第 1 步开始。

选项 2

  1. 使用 Ajax 方法。
  2. 使用 Ajax 调用 JS 文件。
  3. 将结果附加到下面文章中的使用说明中。
  4. setTimeout() 函数到几秒钟并处理脚本或检查类似于选项 #1 的变量并重复超时直到变量存在。

这两种方法都可以参考这篇文章: http://www.hunlock.com/blogs/Howto_Dynamically_Insert_Javascript_And_CSS

困难的部分是您要求您的代码知道浏览器何时加载和处理每个 JS 文件。由于计算机和浏览器的不同,处理 JS 文件所需的时间可能会有所不同,因此如果没有 JS 文件中的某些逻辑来执行此操作,则很难跟踪。

我更喜欢这样的方法:

1。为可以处理动态逻辑(php、java、.NET 等)的服务器制作 Javascript 请求标签、ajax 等。

2。传递一个页面上已经存在的回调函数,并且知道你需要知道多少额外的 JS 文件。甚至在回调中包含一个索引参数(类似于 myCallback(5)),这样你就知道你在哪里。

1-2a。您可以手动将回调函数附加到所有 JS 文件中,让该函数管理 JS 队列,而 JS 文件除了回调函数之外没有任何知识。

3。让服务器将回调附加到正在返回的 JS 文件中。

4。处理完 JS 后会触发回调函数,它会为您管理附加 JS 文件的加载。

怎么样?

【讨论】:

    【解决方案3】:

    跨浏览器工作,从 IE6 开始。

    document.loadScript = function (src, callback) {
        function script(src, onload) {
            var scriptTag = document.createElement('script');
            if (onload) scriptTag.onload = onload;
            scriptTag.src = src;
            scriptTag.type = 'text/javascript';
            return scriptTag;
        }
        function outer(tag) { 
            var d = document.createElement('div');
            d.appendChild(tag);
            return d.innerHTML;
        }
        if (!(src instanceof Array)) src = [src];
        var i, scr, 
            callbackId = 'dynCall_'+Math.floor(Math.random()*89999+10000);
            counter = src.length, 
            call = function () { if (--counter == 0) callback(); };
        if (!document.body) {
            window[callbackId] = function () {
                delete window[callbackId];
                if (callback instanceof Function) callback();
            };
            for (i=0; i<src.length; i++) document.write(outer(script(src[i])));
            document.write('<scr'+'ipt type="text/javascript">'+'window.'+callbackId+'();'+'</scr'+'ipt>');
            return;
        }
        for (i=0; i<src.length; i++) document.body.appendChild(script(src[i], call));
    };
    

    缩小/混淆:

    (function(){document.loadScript=function(src,callback){function script(src,onload){var scriptTag=document.createElement('script');if(onload)scriptTag.onload=onload;scriptTag.src=src;scriptTag.type='text/javascript';return scriptTag}function outer(tag){var d=document.createElement('div');d.appendChild(tag);return d.innerHTML}if(!(src instanceof Array))src=[src];var i,scr,callbackId='dynCall_'+Math.floor(Math.random()*89999+10000);counter=src.length,call=function(){if(--counter==0)callback()};if(!document.body){window[callbackId]=function(){delete window[callbackId];if(callback instanceof Function)callback()};for(i=0;i<src.length;i++)document.write(outer(script(src[i])));document.write('<scr'+'ipt type="text/javascript">'+'window.'+callbackId+'();'+'</scr'+'ipt>');return}for(i=0;i<src.length;i++)document.body.appendChild(script(src[i],call))};document.loadScript.toString=function(){return'function loadScript() { [obfuscated] }'}})();
    

    分支的快速解释:

    如果调用 loadScript 的脚本标签在文档头部或正文中,则 document.body 将是未定义的,因为 DOM 还没有。因此,我们不能使用附加标签的标准方法,而必须使用 doc write。这样做的好处是我们编写的标签必须按顺序发生,但缺点是我们必须有一个全局范围的回调函数。

    同时,如果我们有 document.body,那么我们就是做正确的事(-ish - 当周围没有库来帮助做正确的事情时,我们会做出牺牲,因此 .onload()。不过,它是不像我们要在脚本标签上抛出很多事件,所以它可能没问题。)缺点(也许?)是脚本都是异步加载的,所以我们需要在加载时运行倒计时.

    【讨论】:

    • 要明确一点...如果浏览器缓存了脚本并且实际上并未加载它,是否仍会调用 scr.onload?
    • scr.onload 会被调用,不管是否缓存,只要设置了onload 事件before src 属性。如果没有,缓存会在下一个 JS 命令通过之前捕获。附加:您可能想要拉出现在启动的脚本;我添加了多个脚本支持。
    • Firefox 似乎不支持 outerHTML 属性。
    • 通过 append-to-div-get-innerHTML 修复了 outerHTML 问题
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-08-27
    • 2019-04-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-09-15
    相关资源
    最近更新 更多