同步dynamic加载JavaScript
我正在使用模块模式 ,我想要做的事情之一就是dynamic地包含一个外部JavaScript文件,执行该文件,然后在我的模块的return { }
文件中使用函数/variables。
我无法弄清楚如何轻松做到这一点。 有没有任何标准的方法来执行伪同步外部脚本加载?
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 } }
同步加载和执行脚本资源只有一种方法,那就是使用同步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);
但是你不应该在一般情况下使用同步请求,因为这会阻止其他的事情。 但是,话虽如此,当然这是适当的情况下。
我可能会重构包含的function到一个asynchronous模式,虽然使用onload处理程序。
接受的答案是不正确的。
同步加载文件与同步执行文件不同 – 这是OP所要求的。
接受的答案加载了文件同步,但是除了向DOM添加脚本标签外, 只是因为appendChild()已经返回,并不能保证脚本已经完成执行,并且它的成员被初始化以供使用。
唯一的(见警告)方法来实现OPs的问题是同步加载脚本通过XHR所述,然后阅读文本并传递到eval()或新的函数()调用,并等待该函数返回。 这是保证脚本同步加载和执行的唯一方法。
我不会评论这是从UI还是从安全angular度来看是明智的做法,但肯定会有用例来certificate同步加载和执行。
警告 :除非您使用web worker,在这种情况下只需调用loadScripts();
这是我在我的应用程序中使用多个文件加载的代码。
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. })
我能想到的最类似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
我有这个问题的现有答案(和其他stackoverflow线程上的这个问题的变化)的以下问题:
- 没有加载的代码是可debugging的
- 许多解决scheme需要callback来了解何时加载完成,而不是真正的阻塞,这意味着我会立即调用加载(即加载)代码得到执行错误。
或者更准确地说:
- 没有一个加载的代码是可debugging的(除了HTML脚本标记块,当且仅当解决scheme向dom添加了脚本元素,并且从来没有作为单独的可见脚本)。 =>假设我必须加载多less个脚本和debugging),这是不能接受的。
- 使用“onreadystatechange”或“onload”事件的解决scheme无法阻止,这是一个很大的问题,因为代码最初使用'require([filename,'dojo / domReady'])'同步加载dynamic脚本' 我正在剥离出道场。
我的最终解决scheme是在返回之前加载脚本,并且所有脚本都可以在debugging器中正确访问(至less适用于Chrome),如下所示:
警告:下面的代码应该只能在'开发'模式下使用。 (对于“释放”模式,我build议不要dynamic脚本加载,或者至less不要使用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); } };
var xhrObj = new XMLHttpRequest(); xhrObj.open('GET', '/filename.js', false); xhrObj.send(null); eval(xhrObj.responseText);
如果这是一个跨域请求,它将无法正常工作。 在这种情况下,你必须上传请求的文件到你的服务器,或者做一个镜像PHP输出它,并要求该PHP。
使用jquery(与跨域请求一起工作):
$.getScript('/filename.js',callbackFunction);
callbackFunction
将被同步调用。
为了加载更多脚本,请参阅此主题。
如果您需要加载任意数量的脚本,并且只在最后一个脚本完成时继续执行,并且不能使用XHR(例如,由于CORS限制),则可以执行以下操作。 它不是同步的,但确实允许在最后一个文件加载完成时发生callback:
// 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(在怪癖模式)。 这些IE版本不会触发一个onload
事件,而是为了onreadystatechange
。 IE9在标准模式下触发( onreadystatechange
为所有脚本之前的onload
任何发射)。
基于这个页面 ,旧版本的IE可能不会发送readyState=='complete'
的onreadystatechange
事件。 如果是这种情况(我不能重现这个问题),那么上面的脚本将失败,你的callback将永远不会被调用。
由于显而易见的原因,您不能也不应该同步执行服务器操作。 你可以做什么,但是,有一个事件处理程序告诉你何时加载脚本:
tag.onreadystatechange = function() { if (this.readyState == 'complete' || this.readyState == 'loaded') this.onload({ target: this }); }; tag.onload = function(load) {/*init code here*/}
onreadystatechange
委托是从内存,IE的解决方法,它有onload
补丁支持。
和Sean的答案一样,但不是创build一个脚本标签,而是评估它。 这确保代码实际上可以使用。
我的策略,加载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 "ajax/libs/jquery/3.0.0-alpha1/jquery.min.js", "https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.11.4/jquery-ui.min.js" ] );
在使用Angular时,您可以利用每个提供程序在实例化其他服务之前实例化的事实。 你可以结合这个事实,使用@Neil提到的xhr和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; }; });
要迫使提供者进行初始化,您需要至less注入一个其他指令/服务。 最好这是利用脚本加载的代码的服务。
app.directive('myDirective', ['SomeScriptSyncLoader', function(someScriptSyncLoader) { return { restrict: 'E', link: function(scope, element, attrs) { // some ode }, template: "this is my template" }; }]);
我知道这是一个老问题,但也许别人读这个,并find它的用处! 刚刚创build了一个使用ES6的新组件,以同步方式dynamic加载脚本。 项目的详细信息和源代码在GitHub上https://github.com/amgadfahmi/scripty
我可能迟到回答这个问题。
我目前的解决scheme是recursion添加<script>
标签,以便后续脚本的添加在其前任的callback中。 它假设每个函数都包含一个函数,并且该函数与文件名(减去扩展名)相同。 这可能不是做事情的最好方式,但是可以。
代码要考虑
代码目录结构:
- directory ---- index.html ---- bundle.js ---- test_module/ -------- a.js -------- b.js -------- log_num.js -------- many_parameters.js
的index.html
<head> <script src="bundle.js"></script> </head>
bundle.js
// 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.js
function a() { console.log("a") }
b.js
function b() { console.log("b") }
log_num.js
// it works with parameters too function log_num(num) { console.log(num) }
many_parameters.js
function many_parameters(a, b, c) { var calc = a - b * c console.log(calc) }
实际上有一种方法来加载脚本列表并同步执行它们 。 您需要将每个脚本标记插入到DOM中,明确将其async
属性设置为false:
script.async = false;
已经注入DOM的脚本默认是asynchronous执行的,所以你必须手动设置async
属性为false来解决这个问题。
例
<script> (function() { var scriptNames = [ "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>
参考
- Google的杰克·阿奇博尔德(Jake Archibald)有一篇很棒的文章,叫做深入潜入脚本加载的黑暗水域 。
- 标签上的WHATWG规范是如何加载标签的一个很好和彻底的描述。
我使用jquery 加载方法应用于div元素。 就像是
<div id="js"> <!-- script will be inserted here --> </div> ... $("#js").load("path", function() { alert("callback!" });
您可以多次加载脚本,每次脚本将完全replace之前加载的脚本