我认为标题说明了一切。
代码如下:
<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
事件。
<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
时就会发生这种情况,但是如果设置了script
的innerHTML
,也会发生这种情况。如果在将它们添加到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一样。