如何引用加载当前执行脚本的脚本标记?

如何引用加载当前运行的javascript的脚本元素?

情况是这样的。我有一个“主”脚本被加载在页面的高,第一件事下的头标签。

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<script type="text/javascript" src="scripts.js"></script>

在“scripts.js”中有一个脚本,需要能够按需加载其他脚本。正常的方法不太适合我,因为我需要添加新的脚本而不引用HEAD标记,因为HEAD元素还没有完成渲染:

document.getElementsByTagName('head')[0].appendChild(v);

我要做的是引用加载当前脚本的脚本元素,这样我就可以在它之后将新的动态加载的脚本标记追加到DOM中。

<script type="text/javascript" src="scripts.js"></script>
loaded by scripts.js--><script type="text/javascript" src="new_script1.js"></script>
loaded by scripts.js --><script type="text/javascript" src="new_script2.js"></script>
189581 次浏览

可能最简单的方法是给你的scrip标签一个id属性。

我有这个,它在FF3, IE6 &7. 按需加载脚本中的方法直到页面加载完成才可用,但这仍然非常有用。

//handle on-demand loading of javascripts
makescript = function(url){
var v = document.createElement('script');
v.src=url;
v.type='text/javascript';


//insertAfter. Get last <script> tag in DOM
d=document.getElementsByTagName('script')[(document.getElementsByTagName('script').length-1)];
d.parentNode.insertBefore( v, d.nextSibling );
}

如果您可以假定脚本的文件名,就可以找到它。到目前为止,我只在Firefox中测试过以下功能。

  function findMe(tag, attr, file) {
var tags = document.getElementsByTagName(tag);
var r = new RegExp(file + '$');
for (var i = 0;i < tags.length;i++) {
if (r.exec(tags[i][attr])) {
return tags[i][attr];
}
}
};
var element = findMe('script', 'src', 'scripts.js');

由于脚本是按顺序执行的,因此当前执行的脚本标记始终是页面上的最后一个脚本标记。所以,要获得脚本标签,你可以这样做:

var scripts = document.getElementsByTagName( 'script' );
var thisScriptTag = scripts[ scripts.length - 1 ];

只有当脚本没有“defer”或“async”属性时,才会按顺序执行。知道脚本标记的ID/SRC/TITLE属性之一也可以在这些情况下工作。Greg和Justin的建议都是正确的。

在WHATWG列表中已经有一个document.currentScript的建议。

编辑: Firefox > 4已经实现了这个非常有用的属性,但它在IE11中不可用,最后我检查了,只在Chrome 29和Safari 8中可用。

编辑:没有人提到“文档”。但我相信下面可能是一个很好的跨浏览器的替代方案,以获得当前运行的脚本:

var me = document.scripts[document.scripts.length -1];

它必须工作在页面加载和当一个脚本标签添加javascript(例如,与ajax)

<script id="currentScript">
var $this = document.getElementById("currentScript");
$this.setAttribute("id","");
//...
</script>

遵循以下简单步骤获取当前正在执行的脚本块的引用:

  1. 在脚本块中放入一些随机的唯一字符串(在每个脚本块中必须是唯一的/不同的)
  2. document.getElementsByTagName('script')的迭代结果,从每个内容中查找唯一的字符串(从innerText/textContent属性获得)。

示例(ABCDE345678是唯一的ID):

<script type="text/javascript">
var A=document.getElementsByTagName('script'),i=count(A),thi$;
for(;i;thi$=A[--i])
if((thi$.innerText||thi$.textContent).indexOf('ABCDE345678'))break;
// Now thi$ is refer to current script block
</script>

btw,对于你的情况,你可以简单地使用老式的document.write()方法来包含另一个脚本。 正如你提到的DOM还没有被呈现,你可以利用浏览器总是以线性顺序执行脚本的事实(除了延迟的脚本将在以后呈现),所以你的文档的其余部分仍然是“不存在”的。 你通过document.write()写的任何东西都将放在调用者脚本的后面

原始HTML页面的例子:

<!doctype html>
<html><head>
<script src="script.js"></script>
<script src="otherscript.js"></script>
<body>anything</body></html>

js的内容:

document.write('<script src="inserted.js"></script>');

渲染后,DOM结构将变成:

HEAD
SCRIPT script.js
SCRIPT inserted.js
SCRIPT otherscript.js
BODY

一种处理async &延迟脚本是利用onload处理程序-为所有脚本标记设置一个onload处理程序,第一个执行的应该是你的。

function getCurrentScript(callback) {
if (document.currentScript) {
callback(document.currentScript);
return;
}
var scripts = document.scripts;
function onLoad() {
for (var i = 0; i < scripts.length; ++i) {
scripts[i].removeEventListener('load', onLoad, false);
}
callback(event.target);
}
for (var i = 0; i < scripts.length; ++i) {
scripts[i].addEventListener('load', onLoad, false);
}
}


getCurrentScript(function(currentScript) {
window.console.log(currentScript.src);
});

这里有一个有点填充,它利用document.CurrentScript(如果它存在的话),并返回到通过ID查找脚本。

<script id="uniqueScriptId">
(function () {
var thisScript = document.CurrentScript || document.getElementByID('uniqueScriptId');


// your code referencing thisScript here
());
</script>

如果您在每个脚本标记的顶部包含这个,我相信您将能够始终如一地知道哪个脚本标记正在被触发,并且您还能够在异步回调的上下文中引用脚本标记。

未经测试,所以如果你尝试了,给别人留下反馈。

如何获取当前脚本元素:

1. 使用document.currentScript

document.currentScript将返回当前正在处理其脚本的<script>元素。

<script>
var me = document.currentScript;
</script>

好处

  • 简单明了。可靠的。
  • 不需要修改脚本标记
  • 适用于异步脚本(defer &async)
  • 使用动态插入的脚本

问题

  • 不能在旧的浏览器和IE中工作。
  • 不工作与模块<script type="module">

2. 按id选择脚本

给脚本一个id属性可以让你很容易地从document.getElementById()中通过id来选择它。

<script id="myscript">
var me = document.getElementById('myscript');
</script>

好处

  • 简单明了。可靠的。
  • 几乎得到了普遍支持
  • 适用于异步脚本(defer &async)
  • 使用动态插入的脚本

问题

  • 需要向脚本标记添加自定义属性
  • id属性可能会导致某些浏览器中某些边缘情况下脚本的奇怪行为

3.使用data-*属性选择脚本

给脚本一个data-*属性可以让你很容易地从里面选择它。

<script data-name="myscript">
var me = document.querySelector('script[data-name="myscript"]');
</script>

与前一个选项相比,这几乎没有什么好处。

好处

  • 简单明了。
  • 适用于异步脚本(defer &async)
  • 使用动态插入的脚本

问题

  • 需要向脚本标记添加自定义属性
  • HTML5和querySelector()在所有浏览器中都不兼容
  • 不如使用id属性广泛支持
  • 将使用id边缘情况绕过<script>
  • 如果页面上的另一个元素具有相同的数据属性和值,则可能会混淆。

4. 根据src选择脚本

不使用数据属性,你可以使用选择器根据源来选择脚本:

<script src="//example.com/embed.js"></script>

在embed.js:

var me = document.querySelector('script[src="//example.com/embed.js"]');

好处

  • 可靠的
  • 适用于异步脚本(defer &async)
  • 使用动态插入的脚本
  • 不需要自定义属性或id

问题

  • 是否适用于本地脚本
  • 会在不同的环境中造成问题,比如开发和生产
  • 静止而脆弱。更改脚本文件的位置需要修改脚本
  • 不如使用id属性广泛支持
  • 是否会导致问题,如果你加载相同的脚本两次

5. 遍历所有脚本以找到所需的脚本

我们还可以遍历每个脚本元素,并逐个检查以选择我们想要的元素:

<script>
var me = null;
var scripts = document.getElementsByTagName("script")
for (var i = 0; i < scripts.length; ++i) {
if( isMe(scripts[i])){
me = scripts[i];
}
}
</script>

这让我们可以在不很好地支持querySelector()属性的旧浏览器中使用上述两种技术。例如:

function isMe(scriptElem){
return scriptElem.getAttribute('src') === "//example.com/embed.js";
}

这继承了任何方法的优点和问题,但不依赖于querySelector(),所以在旧的浏览器中也可以工作。

6. 获取最后执行的脚本

由于脚本是按顺序执行的,所以最后一个script元素通常是当前运行的脚本:

<script>
var scripts = document.getElementsByTagName( 'script' );
var me = scripts[ scripts.length - 1 ];
</script>

好处

  • 简单。
  • 几乎得到了普遍支持
  • 不需要自定义属性或id

问题

  • 是否适用于异步脚本(defer &async)
  • 是否与动态插入的脚本一起工作

考虑一下这个算法。当你的脚本加载时(如果有多个相同的脚本),查看文档。脚本,找到第一个具有正确的“src”属性的脚本,并保存它,并使用数据属性或唯一的className将其标记为“visited”。

当加载下一个脚本时,扫描文档。再次执行脚本,传递已经标记为访问过的任何脚本。以该脚本的第一个未访问实例为例。

这假设相同的脚本可能会按照它们加载的顺序执行,从头部到主体,从上到下,从同步到异步。

(function () {
var scripts = document.scripts;


// Scan for this data-* attribute
var dataAttr = 'data-your-attribute-here';


var i = 0;
var script;
while (i < scripts.length) {
script = scripts[i];
if (/your_script_here\.js/i.test(script.src)
&& !script.hasAttribute(dataAttr)) {


// A good match will break the loop before
// script is set to null.
break;
}


// If we exit the loop through a while condition failure,
// a check for null will reveal there are no matches.
script = null;
++i;
}


/**
* This specific your_script_here.js script tag.
* @type {Element|Node}
*/
var yourScriptVariable = null;


// Mark the script an pass it on.
if (script) {
script.setAttribute(dataAttr, '');
yourScriptVariable = script;
}
})();

这将扫描所有脚本,查找第一个没有标记特殊属性的匹配脚本。

然后,如果找到该节点,就用data-属性标记它,这样后续扫描就不会选择它。这类似于图遍历BFS和DFS算法,其中节点可能被标记为“已访问”以防止重访。

获取脚本,即当前加载的脚本可以使用

var thisScript = document.currentScript;

您需要在脚本的开头保留一个引用,以便稍后调用

var url = thisScript.src

我发现下面的代码是最一致、性能最好和最简单的。

var scripts = document.getElementsByTagName('script');
var thisScript = null;
var i = scripts.length;
while (i--) {
if (scripts[i].src && (scripts[i].src.indexOf('yourscript.js') !== -1)) {
thisScript = scripts[i];
break;
}
}
console.log(thisScript);

我用这个通常的eval替代方法动态插入script标记,并在添加到DOM之前简单地设置了一个全局属性currentComponentScript

  const old = el.querySelector("script")[0];
const replacement = document.createElement("script");
replacement.setAttribute("type", "module");
replacement.appendChild(document.createTextNode(old.innerHTML));
window.currentComponentScript = replacement;
old.replaceWith(replacement);

但在循环中不能工作。DOM直到下一个宏任务才运行脚本,因此其中的一批脚本只能看到最后一个值集。你必须setTimeout整个段落,然后在前一段结束后setTimeout下一段。也就是说,串起settimeout,而不是在循环中在一行中多次调用setTimeout