dynamic加载JavaScript文件
你如何可靠和dynamic地加载JavaScript文件? 这可以用来实现一个模块或组件,当“初始化”的组件将dynamic加载所有需要的JavaScript库脚本。
使用该组件的客户端不需要加载实现此组件的所有库脚本文件(并手动插入<script>
标记到他们的网页中) – 只是“主”组件脚本文件。
主streamJavaScript库如何完成这个(Prototype,jQuery等)? 这些工具是否将多个JavaScript文件合并为一个脚本文件的单个可再发行“构build”版本? 还是他们做dynamic加载的辅助“图书馆”脚本?
除了这个问题: 是否有一种方法来处理dynamic包含的JavaScript文件加载后的事件? 原型具有document.observe
文件范围的事件。 例:
document.observe("dom:loaded", function() { // initially hide all containers for tab content $$('div.tabcontent').invoke('hide'); });
脚本元素的可用事件是什么?
您可以编写dynamic脚本标记(使用Prototype ):
new Element("script", {src: "myBigCodeLibrary.js", type: "text/javascript"});
这里的问题是我们不知道何时外部脚本文件被完全加载。
我们经常希望我们的代码在下一行,并且喜欢写下如下的代码:
if (iNeedSomeMore) { Script.load("myBigCodeLibrary.js"); // includes code for myFancyMethod(); myFancyMethod(); // cool, no need for callbacks! }
有一个聪明的方法来注入脚本依赖关系,而不需要callback。 您只需通过同步AJAX请求来提取脚本,并在全局级别评估脚本。
如果使用Prototype,Script.load方法如下所示:
var Script = { _loadedScripts: [], include: function(script) { // include script only once if (this._loadedScripts.include(script)) { return false; } // request file synchronous var code = new Ajax.Request(script, { asynchronous: false, method: "GET", evalJS: false, evalJSON: false }).transport.responseText; // eval code on global level if (Prototype.Browser.IE) { window.execScript(code); } else if (Prototype.Browser.WebKit) { $$("head").first().insert(Object.extend( new Element("script", { type: "text/javascript" }), { text: code } )); } else { window.eval(code); } // remember included script this._loadedScripts.push(script); } };
在javascript中没有import / include / require,但是有两种主要的方法可以实现你想要的function:
1 – 你可以加载一个AJAX调用,然后使用eval。
这是最直接的方法,但由于Javascript的安全设置,它仅限于您的域名,并使用eval打开了错误和黑客的大门。
2 – 在HTML中添加脚本标签。
绝对是最好的方式去。 您甚至可以从外部服务器加载该脚本,并且在使用浏览器分析程序评估代码时它很干净。 您可以将标签放在网页的头部或身体的底部。
这两个解决scheme都在这里讨论和说明。
现在,你必须知道一个大问题。 这意味着你远程加载代码。 现代Web浏览器将加载文件并继续执行当前脚本,因为它们会asynchronous加载所有内容以提高性能。
这意味着,如果直接使用这些技巧,在您要求加载之后,您将无法在下一行使用新加载的代码,因为它仍将加载。
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。 它的工作原理! 混乱…
那么该怎么办呢?
那么,你可以使用作者在我给你的链接build议的黑客。 总之,对于匆忙的人来说,当脚本被加载时,他使用en事件来运行callback函数。 所以你可以把所有使用远程库的代码放在callback函数中。 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 });
最近 jQuery使用了一个简单得多的版本 :
<script src="scripts/jquery.js"></script> <script> var js = ["scripts/jquery.dimensions.js", "scripts/shadedborder.js", "scripts/jqmodal.js", "scripts/main.js"]; var $head = $("head"); for (var i = 0; i < js.length; i++) { $head.append("<script src=\"" + js[i] + "\"></scr" + "ipt>"); } </script>
它在我testing过的每个浏览器都很好用:IE6 / 7,Firefox,Safari,Opera。
更新: jQuery-less版本:
<script> var js = ["scripts/jquery.dimensions.js", "scripts/shadedborder.js", "scripts/jqmodal.js", "scripts/main.js"]; for (var i = 0, l = js.length; i < l; i++) { document.getElementsByTagName("head")[0].innerHTML += ("<script src=\"" + js[i] + "\"></scr" + "ipt>"); } </script>
我做了和亚当基本相同的事情,但是修改了一下幻灯片,以确保我正在追加头标以完成工作。 我只是创build了一个包含函数(代码如下)来处理脚本和css文件。
该函数还检查以确保脚本或CSS文件尚未被dynamic加载。 它不检查手动编码的值,可能有更好的方法来做到这一点,但它的目的。
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 ); }
另一个真棒的答案
$.getScript("my_lovely_script.js", function(){ alert("Script loaded and executed."); // here you can use anything you defined in the loaded script });
以下是我find的一些示例代码…有没有人有更好的方法?
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); }
如果你已经加载了jQuery,你应该使用$ .getScript 。
这比其他答案有一个优点,那就是你有一个内置的callback函数(为了保证脚本在依赖代码运行之前被加载),你可以控制caching。
有没有人有更好的办法?
我认为只要将脚本添加到正文中,将其添加到页面的最后一个节点上会更容易。 这个怎么样:
function include(url) { var s = document.createElement("script"); s.setAttribute("type", "text/javascript"); s.setAttribute("src", url); document.body.appendChild(s); }
我已经使用了另一种解决scheme,我发现在networking上…这一个是creativecommons下,它会检查源是否包含在调用函数之前 …
你可以在这里find这个文件: include.js
/** include - including .js files from JS - bfults@gmail.com - 2005-02-09 ** Code licensed under Creative Commons Attribution-ShareAlike License ** http://creativecommons.org/licenses/by-sa/2.0/ **/ var hIncludes = null; function include(sURI) { if (document.getElementsByTagName) { if (!hIncludes) { hIncludes = {}; var cScripts = document.getElementsByTagName("script"); for (var i=0,len=cScripts.length; i < len; i++) if (cScripts[i].src) hIncludes[cScripts[i].src] = true; } if (!hIncludes[sURI]) { var oNew = document.createElement("script"); oNew.type = "text/javascript"; oNew.src = sURI; hIncludes[sURI]=true; document.getElementsByTagName("head")[0].appendChild(oNew); } } }
刚刚发现了YUI 3中的一个很棒的function(在预览版发布的时候)。 您可以轻松地将依赖关系插入到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(...); } });
为我完美工作,并具有pipe理依赖关系的优势。 有关YUI 2日历的示例,请参阅YUI文档。
我们在工作中使用的技术是使用AJAX请求来请求JavaScript文件,然后eval()返回。 如果你使用的是原型库,他们在Ajax.Request调用中支持这个function。
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>
我写了一个简单的模块,它自动化了在JavaScript中导入/包含模块脚本的工作。 试一试,请留下一些反馈! :)有关代码的详细说明请参阅此博客文章: 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 });
如果您想加载SYNC脚本,则需要将脚本文本直接添加到HTML HEAD标记。 添加它会触发ASYNC加载。 要从外部文件同步加载脚本文本,请使用XHR。 下面是一个快速示例(在本文和其他文章中使用其他答案的部分):
/*sample requires an additional method for array prototype:*/ if (Array.prototype.contains === undefined) { Array.prototype.contains = function (obj) { var i = this.length; while (i--) { if (this[i] === obj) return true; } return false; }; }; /*define object that will wrap our logic*/ var ScriptLoader = { LoadedFiles: [], LoadFile: function (url) { var self = this; if (this.LoadedFiles.contains(url)) return; var xhr = new XMLHttpRequest(); xhr.onload = function () { if (xhr.readyState === 4) { if (xhr.status === 200) { self.LoadedFiles.push(url); self.AddScript(xhr.responseText); } else { if (console) console.error(xhr.statusText); } } }; xhr.open("GET", url, false);/*last parameter defines if call is async or not*/ xhr.send(null); }, AddScript: function (code) { var oNew = document.createElement("script"); oNew.type = "text/javascript"; oNew.textContent = code; document.getElementsByTagName("head")[0].appendChild(oNew); } }; /*Load script file. ScriptLoader will check if you try to load a file that has already been loaded (this check might be better, but I'm lazy).*/ ScriptLoader.LoadFile("Scripts/jquery-2.0.1.min.js"); ScriptLoader.LoadFile("Scripts/jquery-2.0.1.min.js"); /*this will be executed right after upper lines. It requires jquery to execute. It requires a HTML input with id "tb1"*/ $(function () { alert($('#tb1').val()); });
保持它的好,简单,可维护! :]
// 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]); } }
此代码只是一个简短的function示例, 可能需要额外的function才能在任何(或给定)平台上提供全面支持。
所有主要的JavaScript库,如jscript,prototype,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();
有专门为此devise的脚本。
yepnope.js被内置到Modernizr中,而lab.js是一个更优化的(但不太友好的版本。
我不会推荐通过像jquery或prototype这样的大型库来实现这一点,因为脚本加载器的一个主要优点是能够提前加载脚本 – 不必等到jquery和所有dom元素加载之后运行检查以查看是否要dynamic加载脚本。
我迷失在所有这些样本,但今天我需要从我的主要.js加载外部.js,我做到了这一点:
document.write("<script src='https://www.google.com/recaptcha/api.js'></script>");
这是一个简单的callback和IE支持:
function loadScript(url, callback) { var script = document.createElement("script") script.type = "text/javascript"; if (script.readyState) { //IE script.onreadystatechange = function () { if (script.readyState == "loaded" || script.readyState == "complete") { script.onreadystatechange = null; callback(); } }; } else { //Others script.onload = function () { callback(); }; } script.src = url; document.getElementsByTagName("head")[0].appendChild(script); } loadScript("https://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js", function () { //jQuery loaded console.log('jquery loaded'); });
我知道我对这个问题的答案有点晚了,但是,这里是一个伟大的文章http://www.html5rocks.com – 深入潜入脚本加载的黑暗水域 。
在那篇文章中得出的结论是,就浏览器支持而言,dynamic加载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说:我不知道这个“asynchronous”是什么,但是我只是按照添加的顺序执行通过JS添加的脚本。
Safari 5.0说:我明白“asynchronous”,但不明白与JS设置为“假”。 我会马上执行你的脚本,无论顺序如何。
IE <10表示:不知道“asynchronous”,但有一个解决方法使用“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" ])