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

Posted

技术标签:

【中文标题】如何引用加载当前执行脚本的脚本标签?【英文标题】:How may I reference the script tag that loaded the currently-executing script? 【发布时间】:2010-09-29 01:44:00 【问题描述】:

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

情况是这样的。我在页面高处加载了一个“主”脚本,首先在 HEAD 标记下。

<!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>

【问题讨论】:

一句警告:在 DOM 仍在加载时对其进行修改会导致您收到 world of hurt in IE6 & IE7。在页面加载后运行该代码会更好。 看起来它现在可以在 caniuse 上使用:caniuse.com/#feat=document-currentscript 【参考方案1】:

如何获取当前脚本元素:

1。使用document.currentScript

document.currentScript 将返回当前正在处理其脚本的&lt;script&gt; 元素。

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

好处

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

问题

无法在旧版浏览器和 IE 中运行。 不适用于模块&lt;script type="module"&gt;

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 属性相比,支持较少 将绕过&lt;script&gt;id 边缘情况。 如果页面上的另一个元素具有相同的数据属性和值,可能会感到困惑。

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

好处

简单。 几乎普遍支持 无需自定义属性或 ID

问题

是否适用于异步脚本(defer & async是否适用于动态插入的脚本

【讨论】:

这应该是答案。 同意@RoyiNamir。这是最好的答案。 谢谢大家,但你知道我在接受答案后 4 回答了,对吧 :) "document.currentScript" 不适用于我动态加载的脚本,在最新的 chrome/firefox 中返回 null,“最后执行的脚本”工作正常 script 位于插入Shadow DOM 的template 中时不起作用【参考方案2】:

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

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

【讨论】:

这个简单优雅。如果您解压缩 javascript,新的 Google Charts/Visualizations API 中有一个示例。他们从脚本标签中加载 JSON 数据,请参阅:ajax.googleapis.com/ajax/static/modules/gviz/1.0/chart.js 这是个好主意,它通常对我有用。但我应该补充一点,有时我发现它返回了对不同脚本的引用。不知道为什么 - 无法追踪到。因此,我通常采用不同的方法,例如,我硬编码脚本文件的名称,然后查找具有该文件名的脚本标签。 我能想到的一个可能返回错误结果的实例是脚本标签被异步添加到 DOM 时。 是的,这可能会产生不可预测的结果,因此您可以尝试使用选择器:$('script[src*="/mysource.js"]') ??? 在页面加载后加载脚本时不起作用。你可能不会得到正确的标签。【参考方案3】:

可能最简单的做法是为您的脚本标签添加一个id 属性。

【讨论】:

虽然你是对的,但在很多情况下 OP 的问题是有效的,其中有几个是:1)当你在爬行时 2)当你使用客户端的 DOM 时,而他不愿意改变【参考方案4】:

这是一个利用 document.CurrentScript(如果存在)并回退到通过 ID 查找脚本的 polyfill。

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

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

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

未经测试,如果您尝试,请留下反馈给其他人。

【讨论】:

id 属性在 script 元素中是 invalid。这种方法会产生什么样的问题? @n.r. - 不,所有 元素都可以有id 属性。 idclassslot 是在 DOM 级别定义的,而不是在 HTML 级别。如果您转到global attributes in HTML 并滚动查看列表,您会发现“DOM 标准定义了任何命名空间中任何元素的类、id 和槽属性的用户代理要求。”后跟 “可以在所有 HTML 元素上指定 class、id 和 slot 属性。” DOM 规范涵盖了它here。【参考方案5】:

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

WHATWG 列表中已经有一个document.currentScript 的提案。

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

编辑:没有人提到“document.scripts”集合,但我相信以下可能是获取当前正在运行的脚本的一个很好的跨浏览器替代方案:

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

【讨论】:

这是 document.scripts 不是 document.script【参考方案6】:

它必须在页面加载和使用 javascript 添加脚本标签时工作(例如使用 ajax)

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

【讨论】:

【参考方案7】:

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

var thisScript = document.currentScript;

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

var url = thisScript.src

【讨论】:

【参考方案8】:

按照这些简单的步骤获取当前正在执行的脚本块的引用:

    在脚本块中放置一些随机的唯一字符串(在每个脚本块中必须是唯一的/不同的) 迭代 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>

顺便说一句,对于您的情况,您可以简单地使用老式的 document.write() 方法来包含另一个脚本。 正如您提到的 DOM 尚未呈现,您可以利用浏览器始终以线性顺序执行脚本的事实(除了稍后将呈现的延迟脚本),因此您的文档的其余部分仍然“不存在”。 您通过 document.write() 编写的任何内容都将放在调用者脚本之后。

原始 HTML 页面示例

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

script.js 的内容

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

渲染后的DOM结构会变成:

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

【讨论】:

这似乎只适用于内联脚本,不适用于外部脚本。在后一种情况下,所有属性 innerText、text 和 textContent 都是空的。【参考方案9】:

处理异步和延迟脚本的一种方法是利用 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);
);

【讨论】:

【参考方案10】:

考虑这个算法。当您的脚本加载时(如果有多个相同的脚本),请查看 document.scripts,找到第一个具有正确“src”属性的脚本,并将其保存并使用数据属性或唯一类名将其标记为“已访问”。

当下一个脚本加载时,再次扫描 document.scripts,跳过任何已标记为已访问的脚本。获取该脚本的第一个未访问实例。

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

(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;
  
)();

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

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

【讨论】:

欢迎来到 Stack Overflow。您愿意在算法中包含一些代码吗? 你走吧,@Gary99【参考方案11】:

我有这个,它在 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 );

【讨论】:

【参考方案12】:

我使用 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 上一个完成后的下一个。 IE。链接 setTimeouts,而不是在循环中连续多次调用 setTimeout

【讨论】:

【参考方案13】:

如果你可以假设脚本的文件名,你可以找到它。到目前为止,我只在 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');

【讨论】:

非常过时。这可以使用 querySelector 来完成,这是一个简单的单线器!【参考方案14】:

我发现以下代码是最一致、最高效且最简单的。

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);

【讨论】:

以上是关于如何引用加载当前执行脚本的脚本标签?的主要内容,如果未能解决你的问题,请参考以下文章

浅谈script标签中的async和defer

在加载当前脚本时执行脚本

通过 AJAX 加载脚本标签

javascript脚本如何异步加载,有啥作用

脚本加载优化

如何避免多次加载外部库? npm包,browserify和脚本标签