脚本元素上的onload事件只触发一次,即使我改变了src.我能做点什么吗?



我认为标题说明了一切。

代码如下:

<script>
var script = document.createElement('script');
script.onload = function(){
console.log('Loaded');
};
document.querySelector('head').appendChild(script);
</script>
<button onclick="script.src = 'https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js?call_number=' + Date.now(); console.log(script.src)">Load the script no matter how many times we click the button</button>

按两次按钮,检查控制台。onload事件只被调用一次。

这是浏览器的预期行为吗?

我问是因为脚本可能包含基于call_number参数的数据。因为我不能多次调用onload,所以我不得不一次又一次地在文档中插入脚本元素。这是令人沮丧的。

请不要告诉我创建一个函数来插入脚本。那不是我的问题。我的问题是,我被迫从dom中删除/添加脚本。

所以,我再问一次:这是浏览器的正常行为还是我做错了什么?

PS:作为旁注。在iframe元素上,每次单击按钮时都会调用onload事件。

下面是iframe的代码:
<body>
<script>
var iframe = document.createElement('iframe');
iframe.onload = function(){
console.log('Loaded');
};
document.querySelector('body').appendChild(iframe);
</script>
</body>
<button onclick="iframe.src = 'https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js?call_number=' + Date.now(); console.log(iframe.src)">Load the iframe no matter how many times we click the button</button>

很烦人…

发生了什么

并不是onLoad事件只触发一次,而是脚本本身只触发一次。

为了避免意外的副作用,浏览器只在遇到/初始化script标签时触发一次。在您的示例中,当添加src时就会发生这种情况,但是如果设置了scriptinnerHTML,也会发生这种情况。如果在将它们添加到DOM之前设置了其中任何一个,则脚本只在添加完后才会触发。

TL;DR:脚本标签不能被重用。在脚本标记被触发后更改它的任何部分只会导致DOM被更新。脚本本身不会再被触发。

为了强调这一点,使用以下脚本:

<script>
var script = document.createElement('script');
script.innerHTML = `console.log('Loaded');`;
document.querySelector('head').appendChild(script);
script.innerHTML = ``;
</script>
<button onclick="script.innerHTML = `console.log('Loaded');`;">Load the script no matter how many times we click the button</button>

文字"Loaded"将在页面读取innerHTML时立即出现在控制台中,这发生在遇到document.querySelector('head').appendChild(script);时。你也可以在添加innerHTML之前附加script标签,在这种情况下"Loaded"将在设置innerHTML时显示。无论哪种方式,单击按钮都不会产生任何输出,因为该特定脚本标记已经被触发。稍后添加src或使用按钮也不会导致它触发。还要注意,在这种情况下,onLoad将永远不会被触发,因为内联js已经运行,而不是加载src文件。

简单地删除并再次添加相同的元素变量也不会起作用,因为DOM会记住它。重新启动或启动新脚本的唯一方法是生成新的script标记并使用src(首选)或innerHTML初始化它。

建议你可以将你的脚本封装在一个函数中,并将它们添加到一个数组中,你可以像这样跟踪:

<script>
var scripts = [];
function loadScript(callNumber) {
var script = document.createElement('script');
script.src = 'https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js?call_number=' + callNumber;
script.onload = function() {
console.log('Loaded', scripts.length, script.src);
};
scripts.push(script);
document.querySelector('head').appendChild(script);
}
</script>
<button onclick="loadScript(Date.now());">Load the script no matter how many times we click the button</button>

如果你只关心最后一个脚本实例,你可以每次重新赋值一个变量。如果我的理解是正确的,无论出于什么原因(不鼓励或通常必要),您都不希望DOM上有多个此脚本标记,那么您可以将旧的标记作为调用函数的一部分删除。下面的版本应该完全像你希望的那样运行:

<script>
var script;
function loadScript(callNumber) {
if (script) script.remove();
script = document.createElement('script');
script.src = 'https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js?call_number=' + callNumber;
script.onload = function() {
console.log('Loaded', scripts.length, script.src);
};
document.querySelector('head').appendChild(script);
}
</script>
<button onclick="loadScript(Date.now());">Load the script no matter how many times we click the button</button>

我还鼓励您重新评估为什么需要重新加载脚本,并研究在没有调用号码的情况下加载脚本,然后直接从它访问函数是否会更好。运行脚本后,加载后页面仍然可以使用其中的任何函数和变量,您可以直接访问它们。例如,如果你加载的.js文件有一个函数foo(date) {},从onclick调用它,而不是重新运行整个.js文件。

更好的是,如果它是您的代码而不是外部库,则可以从.js文件中向文档添加一个单击侦听器,并在那里执行所有逻辑。

为什么iframe不同

iFrames独立加载整个页面。更新它的src将导致它用自己的DOM加载新文档,不管它是什么,就像你在浏览器中更新url并按Enter一样。