代码之家  ›  专栏  ›  技术社区  ›  Eric Schoonover thSoft

同步动态加载javascript

  •  53
  • Eric Schoonover thSoft  · 技术社区  · 14 年前

    我正在使用 module pattern ,我要做的一件事是动态地包含一个外部的javascript文件,执行该文件,然后使用文件中的函数/变量 return { } 我的模块。

    我想不出怎么容易做到这一点。有没有执行伪同步外部脚本加载的标准方法?

    function myModule() {
        var tag = document.createElement("script");
        tag.type = "text/javascript";
        tag.src = "http://some/script.js";
        document.getElementsByTagName('head')[0].appendChild(tag);
    
        //something should go here to ensure file is loaded before return is executed
    
        return {
            external: externalVariable 
        }
    }
    
    17 回复  |  直到 5 年前
        1
  •  52
  •   Fabio Milheiro    10 年前

    只有一种方法可以同步加载和执行脚本资源,即使用同步XHR

    这是一个如何做到这一点的例子

    // get some kind of XMLHttpRequest
    var xhrObj = createXMLHTTPObject();
    // open and send a synchronous request
    xhrObj.open('GET', "script.js", false);
    xhrObj.send('');
    // add the returned content to a newly created script tag
    var se = document.createElement('script');
    se.type = "text/javascript";
    se.text = xhrObj.responseText;
    document.getElementsByTagName('head')[0].appendChild(se);
    

    但一般情况下,您不应该使用同步请求,因为这样会阻塞其他所有请求。 但也就是说,当然也有适合的场景。

    我可能会使用onload处理程序将包含函数重构为异步模式。

        2
  •  37
  •   Flimm    7 年前

    这个 accepted answer 不是 对的。

    同步加载文件与同步执行文件不同——这是OP请求的。

    接受的答案将加载文件同步,但只会将脚本标记附加到DOM。只是因为 AppDead() 返回的不保证脚本已完成执行,其成员已初始化以供使用。

    实现ops问题的唯一方法(请参阅警告)是在xhr上同步加载脚本,然后以文本形式读取并传递到eval()或新的函数()调用中,然后等待该函数返回。这是确保加载脚本的唯一方法 同步执行。

    对于从用户界面或安全性的角度来看,这是否是明智的做法,我没有做任何评论,但肯定有一些用例可以证明同步加载和执行是正确的。

    告诫 : 除非您使用的是Web工作者,在这种情况下,只需调用loadscripts();

        3
  •  10
  •   Alan Joseph    12 年前

    这是我在应用程序中用于多个文件加载的代码。

    Utilities.require = function (file, callback) {
        callback = callback ||
        function () {};
        var filenode;
        var jsfile_extension = /(.js)$/i;
        var cssfile_extension = /(.css)$/i;
    
        if (jsfile_extension.test(file)) {
            filenode = document.createElement('script');
            filenode.src = file;
            // IE
            filenode.onreadystatechange = function () {
                if (filenode.readyState === 'loaded' || filenode.readyState === 'complete') {
                    filenode.onreadystatechange = null;
                    callback();
                }
            };
            // others
            filenode.onload = function () {
                callback();
            };
            document.head.appendChild(filenode);
        } else if (cssfile_extension.test(file)) {
            filenode = document.createElement('link');
            filenode.rel = 'stylesheet';
            filenode.type = 'text/css';
            filenode.href = file;
            document.head.appendChild(filenode);
            callback();
        } else {
            console.log("Unknown file type to load.")
        }
    };
    
    Utilities.requireFiles = function () {
        var index = 0;
        return function (files, callback) {
            index += 1;
            Utilities.require(files[index - 1], callBackCounter);
    
            function callBackCounter() {
                if (index === files.length) {
                    index = 0;
                    callback();
                } else {
                    Utilities.requireFiles(files, callback);
                }
            };
        };
    }();
    

    这个实用程序可以被

    Utilities.requireFiles(["url1", "url2",....], function(){
        //Call the init function in the loaded file.
        })
    
        4
  •  5
  •   Jaggler3 Venkat Esan    8 年前

    我能想到的大多数类似node.js的实现都能够同步加载JS文件,并将它们用作对象/模块。

    var scriptCache = [];
    var paths = [];
    function Import(path)
    {
        var index = 0;
        if((index = paths.indexOf(path)) != -1) //If we already imported this module
        {
            return scriptCache [index];
        }
    
        var request, script, source;
        var fullPath = window.location.protocol + '//' + window.location.host + '/' + path;
    
        request = new XMLHttpRequest();
        request.open('GET', fullPath, false);
        request.send();
    
        source = request.responseText;
    
        var module = (function concealedEval() {
            eval(source);
            return exports;
        })();
    
        scriptCache.push(module);
        paths.push(path);
    
        return module;
    }
    

    示例源( addobjects.js ):

    function AddTwoObjects(a, b)
    {
        return a + b;
    }
    
    this.exports = AddTwoObjects;
    

    像这样使用:

    var AddTwoObjects = Import('addobjects.js');
    alert(AddTwoObjects(3, 4)); //7
    //or even like this:
    alert(Import('addobjects.js')(3, 4)); //7
    
        5
  •  3
  •   jeremykentbgross    10 年前

    我对这个问题的现有答案(以及这个问题在其他stackoverflow线程上的变化)有以下问题:

    • 加载的代码都不可调试
    • 许多解决方案都需要回调来知道何时加载完成,而不是真正的阻塞,这意味着我将从立即调用已加载(即加载)代码中得到执行错误。

    或者更准确地说:

    • 加载的代码都不可调试 (除了HTML脚本标记块之外,如果并且仅当解决方案向DOM添加了脚本元素,并且从未作为单独的可查看脚本添加。) =>考虑到我必须加载(和调试)多少脚本,这是不可接受的。
    • 使用“onreadyStateChange”或“onload”事件的解决方案未能阻止,这是一个很大的问题,因为代码最初使用“require([文件名,'dojo/domready']);同步加载动态脚本,而我正在剥离dojo。

    我的最后一个解决方案是,在返回之前加载脚本,并在调试器中正确地访问所有脚本(至少对于chrome而言),如下所示:

    警告:以下代码可能只能在“开发”模式下使用。 (对于“发布”模式,我建议在不加载动态脚本或至少不加载eval的情况下进行预打包和缩小)。

    //Code User TODO: you must create and set your own 'noEval' variable
    
    require = function require(inFileName)
    {
        var aRequest
            ,aScript
            ,aScriptSource
            ;
    
        //setup the full relative filename
        inFileName = 
            window.location.protocol + '//'
            + window.location.host + '/'
            + inFileName;
    
        //synchronously get the code
        aRequest = new XMLHttpRequest();
        aRequest.open('GET', inFileName, false);
        aRequest.send();
    
        //set the returned script text while adding special comment to auto include in debugger source listing:
        aScriptSource = aRequest.responseText + '\n////# sourceURL=' + inFileName + '\n';
    
        if(noEval)//<== **TODO: Provide + set condition variable yourself!!!!**
        {
            //create a dom element to hold the code
            aScript = document.createElement('script');
            aScript.type = 'text/javascript';
    
            //set the script tag text, including the debugger id at the end!!
            aScript.text = aScriptSource;
    
            //append the code to the dom
            document.getElementsByTagName('body')[0].appendChild(aScript);
        }
        else
        {
            eval(aScriptSource);
        }
    };
    
        6
  •  2
  •   Community gkalpak    7 年前
    var xhrObj = new XMLHttpRequest();
    xhrObj.open('GET', '/filename.js', false);
    xhrObj.send(null);
    eval(xhrObj.responseText);
    

    如果这是一个跨域请求,它将不起作用。在这种情况下,您必须将请求的文件上传到服务器,或者创建一个输出文件的镜像PHP,并需要该PHP。

    使用jquery(也适用于跨域请求):

    $.getScript('/filename.js',callbackFunction);
    

    callbackFunction 将被同步调用。

    要加载更多脚本,请参见 this 线程。

        7
  •  1
  •   Phrogz    12 年前

    如果需要加载任意数量的脚本,并且只在最后一个脚本完成时继续,并且不能使用XHR(例如,由于CORS限制),那么可以执行以下操作。它不是同步的,但允许在最后一个文件加载完成时进行回调:

    // Load <script> elements for all uris
    // Invoke the whenDone callback function after the last URI has loaded
    function loadScripts(uris,whenDone){
      if (!uris.length) whenDone && whenDone();
      else{
        for (var wait=[],i=uris.length;i--;){
          var tag  = document.createElement('script');
          tag.type = 'text/javascript';
          tag.src  = uris[i];
          if (whenDone){
            wait.push(tag)
            tag.onload = maybeDone; 
            tag.onreadystatechange = maybeDone; // For IE8-
          }
          document.body.appendChild(tag);
        }
      }
      function maybeDone(){
        if (this.readyState===undefined || this.readyState==='complete'){
          // Pull the tags out based on the actual element in case IE ever
          // intermingles the onload and onreadystatechange handlers for the same
          // script block before notifying for another one.
          for (var i=wait.length;i--;) if (wait[i]==this) wait.splice(i,1);
          if (!wait.length) whenDone();
        }
      }
    }
    

    编辑 :更新为使用IE7、IE8和IE9(在Quirks模式下)。这些IE版本不会触发 onload 事件,但为 onreadystatechange . IE9标准模式火灾 二者都 OnReadyStateChange(状态更改) 所有脚本在 重载 对于任何)。

    基于 this page 旧版本的IE可能永远不会发送 OnReadyStateChange(状态更改) 事件与 readyState=='complete' ;如果是这种情况(我无法重现此问题),则上述脚本将失败,并且永远不会调用您的回调。

        8
  •  1
  •   Flimm    7 年前

    实际上有一种方法可以加载脚本列表 然后执行它们 同步地。您需要将每个脚本标记插入到DOM中,显式地设置它的 async 属性为假:

    script.async = false;
    

    默认情况下,注入到DOM中的脚本是异步执行的,因此必须设置 异步的 手动将属性设置为false以解决此问题。

    例子

    <script>
    (function() {
      var scriptNames = [
        "https://code.jquery.com/jquery.min.js",
        "example.js"
      ];
      for (var i = 0; i < scriptNames.length; i++) {
        var script = document.createElement('script');
        script.src = scriptNames[i];
        script.async = false; // This is required for synchronous execution
        document.head.appendChild(script);
      }
      // jquery.min.js and example.js will be run in order and synchronously
    })();
    </script>
    
    <!-- Gotcha: these two script tags may still be run before `jquery.min.js`
         and `example.js` -->
    <script src="example2.js"></script>
    <script>/* ... */<script>
    

    工具书类

        9
  •  1
  •   Nir O.    6 年前

    接受的答案不正确:

    这个 script.async = false; 指令只意味着在脚本执行期间将暂停HTML解析。这并不能保证javascript代码的运行顺序。看见 https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/loading-third-party-javascript/

    这里还没有提到的最简单和最优雅的解决方案是使用承诺,比如:

        function loadScript(url) {
          return new Promise((resolve, reject) => {
            var script = document.createElement('script')
            script.src = url
            script.onload = () => {
              resolve()
            }
            script.onerror = () => {
              reject('cannot load script '+ url)
            }
            document.body.appendChild(script)
          })
        }
    

    然后,当您希望按顺序执行脚本时:

            loadScript('myfirstscript.js').then(() => {
              console.log('first script ran');
              loadScript('index.js').then(() => {
                console.log('second script ran');
              })
            })
    
        10
  •  0
  •   Igor Zevaka    14 年前

    不能和 由于明显的原因,不应该同步执行服务器操作。不过,您可以做的是让一个事件处理程序告诉您脚本何时加载:

    tag.onreadystatechange = function() { if (this.readyState == 'complete' || this.readyState == 'loaded') this.onload({ target: this }); };
    
    tag.onload = function(load) {/*init code here*/}
    

    onreadystatechange 根据记忆,授权是IE的一个解决方案,它对 onload .

        11
  •  0
  •   Peter Hawkins    12 年前

    与Sean的答案相同,但不要创建脚本标记,只需评估它。这样可以确保代码实际上已经准备好使用了。

        12
  •  0
  •   Leonardo Ciaccio    9 年前

    我的策略,经典的例子,当加载jquery ui时,我希望这能帮助您

    ( function( tools, libs ){
    	
        // Iterator
        var require = function( scripts, onEnd ){
            
            onEnd = onEnd || function(){};
            
            if( !scripts || scripts.length < 1 )return onEnd();
            
            var src    = scripts.splice( 0, 1),
                script = document.createElement( "script" );
            
            script.setAttribute( "src", src );
            
            tools.addEvent( "load", script, function(){
                
                require( scripts, onEnd );
                
            } );
            
            document.getElementsByTagName( "head" )[ 0 ].appendChild( script );
            
        };
        
        // Install all scripts with a copy of scripts
        require( libs.slice(), function(){
        
            alert( "Enjoy :)" );
        
        } );
        
        // Timeout information
        var ti = setTimeout( function(){
            
            if( !window.jQuery || !window.jQuery.ui )alert( "Timeout !" );
            
            clearTimeout( ti );
            
        }, 5000 );
    
    } )(
    
        { // Tools
        
            addEvent : function( evnt, elem, func ){
            
                try{
    
                    if( elem.addEventListener ){
    
                        elem.addEventListener( evnt, func, false );
    
                    }else if( elem.attachEvent ){
    
                         var r = elem.attachEvent( "on" + evnt, func );
    
                    }
    
                    return true;
    
                }catch( e ){
    
                    return false;
    
                }		    
    
            }
        
        },
        [ // Scripts
        
            "https://cdnjs.cloudflare.com/ajax/libs/jquery/3.0.0-alpha1/jquery.min.js",
            "https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.11.4/jquery-ui.min.js"
            
        ]
    
    );
        13
  •  0
  •   walkeros    8 年前

    使用Angular时,可以利用这样一个事实:在实例化其他服务之前,每个提供程序都已实例化。您可以将这一事实与使用xhr和@neil提到的eval()结合起来。代码如下:

    app.provider('SomeScriptSyncLoader', function() {
    
        var resourceUrl =  'http://some/script.js';
        var dummy = {};
    
        this.$get = function() {
    
            var q = jQuery.ajax({
                type: 'GET', url: resourceUrl, cache: false, async: false
            });
    
            if (q.status === 200) {
                eval(q.responseText); // execute some script synchronously as inline script - eval forces sync processing
            }
            return dummy;
        };
    });
    

    要强制使提供者独立化,您需要将其注入至少一个其他指令/服务中。最好是利用脚本加载的代码的服务。

    app.directive('myDirective', ['SomeScriptSyncLoader', function(someScriptSyncLoader) {
    
    return {
        restrict: 'E',
        link: function(scope, element, attrs) {
            // some ode
        },
        template: "this is my template"
       };
    }]);
    
        14
  •  0
  •   Amgad Fahmi    8 年前

    我知道这是一个古老的问题,但也许有人读了这个,发现它有用! 刚创建的新组件使用ES6以同步方式动态加载脚本。 项目详细信息和源代码在Github上 https://github.com/amgadfahmi/scripty

        15
  •  0
  •   SumNeuron    7 年前

    我回答这个问题可能会迟到。

    我当前的解决方案是递归地添加 <script> 标记,以便在其前置脚本的回调中添加后续脚本。它假定每个函数包含一个函数,并且该函数与文件名相同(减去扩展名)。这可能不是做事情的最佳方式,但它工作正常。

    要考虑的代码

    代码目录结构:

    - directory
    ---- index.html
    ---- bundle.js
    ---- test_module/
    -------- a.js
    -------- b.js
    -------- log_num.js
    -------- many_parameters.js
    

    索引文件

    <head>
      <script src="bundle.js"></script>
    </head>
    

    J.

    // Give JS arrays the .empty() function prototype
    if (!Array.prototype.empty){
        Array.prototype.empty = function(){
            return this.length == 0;
        };
    };
    
    function bundle(module_object, list_of_files, directory="") {
      if (!list_of_files.empty()) {
        var current_file = list_of_files.pop()
        var [function_name, extension] = current_file.split(".")
        var new_script = document.createElement("script")
        document.head.appendChild(new_script)
    
        new_script.src = directory + current_file
    
        new_script.onload = function() {
          module_object[function_name] = eval(function_name)
          bundle(module_object, list_of_files, directory)
          /*
          nullify the function in the global namespace as - assumed -  last
          reference to this function garbage collection will remove it. Thus modules
          assembled by this function - bundle(obj, files, dir) - must be called
          FIRST, else one risks overwritting a funciton in the global namespace and
          then deleting it
          */
          eval(function_name + "= undefined")
        }
      }
    }
    
    var test_module = {}
    bundle(test_module, ["a.js", "b.js", "log_num.js", "many_parameters.js"], "test_module/")
    

    A.J.

    function a() {
      console.log("a")
    }
    

    B.JS

    function b() {
      console.log("b")
    }
    

    LogyNo.js

    // it works with parameters too
    function log_num(num) {
      console.log(num)
    }
    

    多个参数.js

    function many_parameters(a, b, c) {
      var calc = a - b * c
      console.log(calc)
    }
    
        16
  •  0
  •   SND-KNN    5 年前

    这是我的密码

    var loaded_script = [];
    function loadScript(urls, callback, sync) {
        var len = urls.length, count = 0;
    
        // check are all js loaded, then execute callback (if any)
        var check = function() {
            if (count == len) {
                callback && typeof callback=="function" && callback();
            }
        };
    
        for (var i = 0; i < len; i++) {
            var url = urls[i];
    
            // check if script not loaded (prevent load again)
            if (loaded_script.indexOf(url) == -1) {
                var script = document.createElement("script");
                script.type = "text/javascript";
    
                // set sync loading here (default is async)
                if (sync) {
                    script.async = false;
                }
    
                // script onload event
                if (script.readyState) {    // IE
                    script.onreadystatechange = function() {
                        if (script.readyState=="loaded" || script.readyState=="complete") {
                            script.onreadystatechange = null;
                            count++, check();
                        }
                    };
                } else {    // Others
                    script.onload = function() {
                        count++, check();
                    };
                }
    
                // add script to head tag
                script.src = url;
                document.getElementsByTagName("head")[0].appendChild(script);
    
                // mark this script has loaded
                loaded_script.push(url);
            } else {
                count++, check();
            }
        }
    }
    

    我在PJAX网站上使用这个。

    loadScript(
        [
            "js/first.js",
            "js/second.js",
        ],
        function() {
            alert("Scripts loaded.");
        },
        true
    );
    
        17
  •  -1
  •   Andrew Florko    14 年前

    我使用jQuery 负载 方法应用于DIV元素。类似的东西

    <div id="js">
    <!-- script will be inserted here --> 
    </div>
    
    ...
    
    $("#js").load("path", function() {  alert("callback!" });
    

    您可以多次加载脚本,每次一个脚本将完全替换先前加载的脚本