是否可以同步调用.js
文件,然后立即使用它?
<script type="text/javascript">
var head = document.getElementsByTagName('head').item(0);
var script = document.createElement('script');
script.setAttribute('type', 'text/javascript');
script.setAttribute('src', 'http://mysite/my.js');
head.appendChild(script);
myFunction(); // Fails because it hasn't loaded from my.js yet.
window.onload = function() {
// Works most of the time but not all of the time.
// Especially if my.js injects another script that contains myFunction().
myFunction();
};
</script>
这是简化的。在我的实现中,createElement的东西在一个函数中。我想在函数中添加一些东西,可以在返回控件之前检查某个变量是否被实例化。但是,当我从另一个我无法控制的网站中包含js时,仍然存在如何处理的问题。
思想?
编辑:
我现在接受了最好的答案,因为它为正在发生的事情提供了一个很好的解释。但如果有人对如何改进这一点有任何建议我会向他们开放。这是我想要做的一个例子。
// Include() is a custom function to import js.
Include('my1.js');
Include('my2.js');
myFunc1('blarg');
myFunc2('bleet');
我只是想避免过多地了解内部结构,并且能够说,“我希望使用这个模块,现在我将使用它的一些代码。”
答案 0 :(得分:111)
您可以使用“onload”处理程序创建<script>
元素,并在浏览器加载和评估脚本时调用该元素。
var script = document.createElement('script');
script.onload = function() {
alert("Script loaded and ready");
};
script.src = "http://whatever.com/the/script.js";
document.getElementsByTagName('head')[0].appendChild(script);
你无法同步。
编辑 - 有人指出,对于形式而言,IE不会在正在加载/评估的<script>
标记上触发“加载”事件。因此,我认为接下来要做的就是使用XMLHttpRequest获取脚本,然后自己eval()
。 (或者,我想,将文本填充到您添加的<script>
标记中; eval()
的执行环境受本地范围的影响,因此它不一定会按照您希望的方式执行。 )
编辑 - 截至2013年初,我强烈建议您查看更强大的脚本加载工具,例如Requirejs。有很多特殊情况需要担心。对于非常简单的情况,有yepnope,现在内置于Modernizr。
答案 1 :(得分:23)
这不太好,但它有效:
<script type="text/javascript">
document.write('<script type="text/javascript" src="other.js"></script>');
</script>
<script type="text/javascript">
functionFromOther();
</script>
或者
<script type="text/javascript">
document.write('<script type="text/javascript" src="other.js"></script>');
window.onload = function() {
functionFromOther();
};
</script>
该脚本必须包含在单独的<script>
代码中或window.onload()
之前。
这不起作用:
<script type="text/javascript">
document.write('<script type="text/javascript" src="other.js"></script>');
functionFromOther(); // Error
</script>
与Pointy一样,创建节点也是如此,但仅限于FF。您无法保证脚本何时可以在其他浏览器中使用。
作为XML纯粹主义者我真的很讨厌这个。但它确实可行。您可以轻松地包裹那些丑陋的document.write()
,这样您就不必查看它们了。您甚至可以进行测试并创建一个节点并将其追加,然后再回到document.write()
。
答案 2 :(得分:16)
这是迟到的,但为了将来参考任何想要这样做的人,您可以使用以下内容:
function require(file,callback){
var head=document.getElementsByTagName("head")[0];
var script=document.createElement('script');
script.src=file;
script.type='text/javascript';
//real browsers
script.onload=callback;
//Internet explorer
script.onreadystatechange = function() {
if (this.readyState == 'complete') {
callback();
}
}
head.appendChild(script);
}
我前段时间做了一篇简短的博文http://crlog.info/2011/10/06/dynamically-requireinclude-a-javascript-file-into-a-page-and-be-notified-when-its-loaded/
答案 3 :(得分:7)
异步编程稍微更强复杂,因为后果 发出请求的请求封装在函数中,而不是跟在请求语句之后。 但 用户体验的实时行为可能显着 更好因为他们不会看到服务器缓慢或网络迟缓导致 浏览器表现得好像已经崩溃了。 同步编程不尊重 在人们使用的应用程序中不应使用 。
Douglas Crockford (YUI Blog)
好吧,扣上你的座位,因为这将是一个坎坷的旅程。越来越多的人质疑通过javascript动态加载脚本,这似乎是一个热门话题。
其成为如此受欢迎的主要原因是:
关于模块化:很明显,应该在客户端正确处理客户端依赖关系。如果需要某个对象,模块或库,我们只需要它并动态加载它。
错误处理:如果资源失败,我们仍然有机会仅阻止依赖受影响脚本的部分,或者甚至可能会再次尝试延迟。
性能已成为网站之间的竞争优势,现在已成为搜索排名因素。动态脚本可以做的是模仿异步行为,而不是浏览器处理脚本的默认阻塞方式。 脚本阻止其他资源,脚本阻止进一步解析HTML文档,脚本阻止 UI。现在使用动态脚本标记及其跨浏览器替代方案,您可以执行真正的异步请求,并仅在相关代码可用时执行它们。即使使用其他资源,您的脚本也会并行加载,渲染将完美无瑕。
有些人坚持使用同步脚本的原因是因为他们习惯了它。他们认为这是默认方式,它是更容易的方式,有些人甚至认为这是唯一的方法。
但是,当需要就应用程序的设计做出决定时,我们唯一需要关心的是最终用户体验。而在这个领域异步不能打败。用户获得即时回复(或说承诺),承诺永远比没有好。一个空白的屏幕吓到了人们。开发人员不应该懒于提高感知性能。
最后还有一些关于肮脏方面的话。你应该做些什么才能让它在浏览器中运行:
答案 4 :(得分:4)
上面的答案指出了我正确的方向。这是我工作的通用版本:
var script = document.createElement('script');
script.src = 'http://' + location.hostname + '/module';
script.addEventListener('load', postLoadFunction);
document.head.appendChild(script);
function postLoadFunction() {
// add module dependent code here
}
答案 5 :(得分:3)
这看起来像动态脚本加载的一个不错的概述: http://unixpapa.com/js/dyna.html
答案 6 :(得分:2)
我对此问题的现有答案存在以下问题(以及此问题在其他stackoverflow线程上的变体):
或者,更准确一点:
我的最终解决方案,在返回之前加载脚本,并且在调试器中可以正确访问所有脚本(至少对于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);
}
};
答案 7 :(得分:2)
function include(file){
return new Promise(function(resolve, reject){
var script = document.createElement('script');
script.src = file;
script.type ='text/javascript';
script.defer = true;
document.getElementsByTagName('head').item(0).appendChild(script);
script.onload = function(){
resolve()
}
script.onerror = function(){
reject()
}
})
/*I HAVE MODIFIED THIS TO BE PROMISE-BASED
HOW TO USE THIS FUNCTION
include('js/somefile.js').then(function(){
console.log('loaded');
},function(){
console.log('not loaded');
})
*/
}
答案 8 :(得分:1)
我习惯在我的网站上有多个依赖于另一个的.js文件。要加载它们并确保以正确的顺序评估依赖项,我编写了一个函数来加载所有文件,然后,一旦收到它们,就eval()
它们。主要缺点是,因为这不适用于CDN。对于这样的库(例如,jQuery),最好静态地包含它们。请注意,在HTML 动态中插入脚本节点并不能保证脚本按正确的顺序进行评估,至少不能在Chrome中进行评估(这是编写此功能的主要原因)。
function xhrs(reqs) {
var requests = [] , count = [] , callback ;
callback = function (r,c,i) {
return function () {
if ( this.readyState == 4 ) {
if (this.status != 200 ) {
r[i]['resp']="" ;
}
else {
r[i]['resp']= this.responseText ;
}
c[0] = c[0] - 1 ;
if ( c[0] == 0 ) {
for ( var j = 0 ; j < r.length ; j++ ) {
eval(r[j]['resp']) ;
}
}
}
}
} ;
if ( Object.prototype.toString.call( reqs ) === '[object Array]' ) {
requests.length = reqs.length ;
}
else {
requests.length = 1 ;
reqs = [].concat(reqs);
}
count[0] = requests.length ;
for ( var i = 0 ; i < requests.length ; i++ ) {
requests[i] = {} ;
requests[i]['xhr'] = new XMLHttpRequest () ;
requests[i]['xhr'].open('GET', reqs[i]) ;
requests[i]['xhr'].onreadystatechange = callback(requests,count,i) ;
requests[i]['xhr'].send(null);
}
}
我还没有想出如何在不创建数组(用于计数)的情况下引用相同的值。否则我认为这是不言自明的(当所有内容都被加载时,eval()
按照给定的顺序每个文件,否则只是存储响应。)
用法示例:
xhrs( [
root + '/global.js' ,
window.location.href + 'config.js' ,
root + '/js/lib/details.polyfill.min.js',
root + '/js/scripts/address.js' ,
root + '/js/scripts/tableofcontents.js'
]) ;
答案 9 :(得分:1)
// ...
await import_script('https://cdnjs.cloudflare.com/ajax/libs/...js');
async function import_script(url) {
const script = document.createElement('script');
script.type = 'text/javascript';
script.src = url;
document.head.appendChild(script);
console.log(`import ${url} ...`);
await new Promise((resolve) => script.onload = resolve);
}
答案 10 :(得分:0)
我正在动态地和异步地加载东西,但是使用load
回调就像这样(使用dojo和xmlhtpprequest)
dojo.xhrGet({
url: 'getCode.php',
handleAs: "javascript",
content : {
module : 'my.js'
},
load: function() {
myFunc1('blarg');
},
error: function(errorMessage) {
console.error(errorMessage);
}
});
有关更详细的说明,请参阅here
问题在于代码被篡改的某个地方,如果代码有任何问题,console.error(errorMessage);
语句将指示eval()
所在的行,而不是实际错误。这是一个很大的问题,我实际上试图将其转换回<script>
语句(请参阅here。
答案 11 :(得分:0)
这适用于支持async/await和fetch的现代“常绿” 浏览器。
此示例经过简化,没有错误处理,可以显示工作中的基本原理。
// This is a modern JS dependency fetcher - a "webpack" for the browser
const addDependentScripts = async function( scriptsToAdd ) {
// Create an empty script element
const s=document.createElement('script')
// Fetch each script in turn, waiting until the source has arrived
// before continuing to fetch the next.
for ( var i = 0; i < scriptsToAdd.length; i++ ) {
let r = await fetch( scriptsToAdd[i] )
// Here we append the incoming javascript text to our script element.
s.text += await r.text()
}
// Finally, add our new script element to the page. It's
// during this operation that the new bundle of JS code 'goes live'.
document.querySelector('body').appendChild(s)
}
// call our browser "webpack" bundler
addDependentScripts( [
'https://code.jquery.com/jquery-3.5.1.slim.min.js',
'https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js'
] )