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

386

我如何引用当前正在运行的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);
我想要做的是引用加载当前脚本的script元素,这样我就可以在它后面将我的新动态加载的脚本标签添加到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>

1
警告:在DOM仍在加载时修改它将会导致IE6和IE7的问题。最好在页面加载后运行该代码,以免出现麻烦。 - Kenan Banks
8
现在看起来已经可以在 caniuse 网站上查看了:http://caniuse.com/#feat=document-currentscript - Tyler
14个回答

836

如何获取当前的脚本元素:

1. 使用 document.currentScript

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

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

好处

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

问题

  • 不支持旧版浏览器和IE。
  • 不适用于模块 <script type="module">

2. 通过id选择脚本

给脚本加上id属性可以让您方便地通过document.getElementById()选择它。

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

好处

  • 简单易用。
  • 几乎被所有浏览器支持。
  • 不需要自定义属性或 ID。

问题

  • 不支持异步脚本 (defer & async)。
  • 不能与动态插入的脚本一起使用。

7
这应该是答案。 - Royi Namir
33
谢谢大家,但是你们知道我在被接受的回答之后整整回答了4年,对吧 :) - brice
6
“document.currentScript”在我使用动态加载的脚本时无法正常工作,在最新版本的Chrome/Firefox中返回null,“最后执行的脚本”可以正常工作。 - xwild
3
script标签位于插入在Shadow DOM中的模板中时,代码将无法运行。 - Supersharp
3
非常尊重@brice发表的关于4年后接受答案的谦虚评论(我是在发布9年后才发表这条评论,也不知道那个被接受的答案去了哪里……)。这是一个出色的答案,对我有很大帮助。这就是SO社区的精髓所在,生产者们重新回答旧帖子,并用这些类型的答案使像我这样偶然发现这些陈旧帖子的消费者获得宝贵的信息。感谢您,先生! - Psyrus
显示剩余13条评论

90

由于脚本是按顺序执行的,因此当前执行的脚本标签直到那时始终是页面上的最后一个脚本标签。因此,要获取脚本标签,您可以执行以下操作:

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

3
这很简单而优雅。如果你解包javascript,就可以在新的Google Charts/Visualizations API中找到一个例子。他们从脚本标签内部加载JSON数据,请参见:http://ajax.googleapis.com/ajax/static/modules/gviz/1.0/chart.js - Jason Thrasher
2
这是一个很棒的想法,通常对我来说都很有效。但我也要补充说明,有时候它会返回一个指向不同脚本的引用。我不确定为什么 - 一直追踪不到原因。因此,我通常会采用不同的方法,例如我会硬编码脚本文件的名称,并查找具有该文件名的脚本标记。 - Ken Smith
55
我能想到的一个可能会返回不正确结果的情况是当一个脚本标签被异步地添加到DOM中。 - Coffee Bite
9
是的,这可能会产生不可预测的结果,所以你可以尝试使用选择器:$('script[src*="/mysource.js"]') 代替。 - King Friday
7
如果页面加载完成后再加载脚本,它可能无法正常工作。你可能无法得到正确的标签。 - ThemeZ
显示剩余5条评论

17

最简单的方法可能是给你的脚本标签添加一个id属性。


3
虽然你是正确的,但有很多情况下原帖提出的问题是合理的,以下是其中的两个例子: 1)当你在抓取数据时 2)当你正在处理客户端的文档对象模型(DOM),而客户不愿更改。 - nichochar

12
只有在没有"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];

1
它是document.scripts而不是document.script。 - Moritz
当一些测试失败时,我们发现在FF4中document.scripts不存在。因此,如果您想要旧浏览器支持,最好使用document.getElementsByTagName('script') - mattpr

12

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

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

        // your code referencing thisScript here
    ());
</script>
如果您在每个脚本标签的顶部包含此代码,我相信您将能够始终知道哪个脚本标签正在被触发,并且您还可以在异步回调的上下文中引用该脚本标签。
未经测试,如果您尝试,请留下反馈给其他人。

id属性虽然在script元素中无效。那么这种方法可能会引发哪些问题呢? - n.r.
2
@n.r. - 不,所有元素都可以有id属性。 idclassslot是在DOM级别而不是HTML级别定义的。 如果您转到HTML中的全局属性并向下滚动列表,您会发现“DOM标准定义了任何命名空间中任何元素的类,id和slot属性的用户代理要求。”后跟“所有HTML元素都可以指定class,id和slot属性。”* DOM规范在此处进行了说明:https://html.spec.whatwg.org/multipage/dom.html#global-attributes。 - T.J. Crowder

8

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

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

6

要获取当前加载脚本的脚本,您可以使用以下代码

var thisScript = document.currentScript;

你需要在脚本开头保留一个引用,这样你才能在后面调用它。
var url = thisScript.src

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

4

按照以下简单步骤获取当前执行脚本块的引用:

  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>

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

原始HTML页面示例

<!doctype html>
<html><head>
<script src="script.js"></script>
<script src="otherscript.js"></script>
<body>anything</body></html>
document.write('<script src="inserted.js"></script>');

渲染后,DOM结构将变为:

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

这似乎只适用于内联脚本,而不适用于外部脚本。在后一种情况下,所有属性innerText、text和textContent都为空。 - Jos de Jong

3
考虑以下算法。当您的脚本加载时(如果有多个相同的脚本),请浏览 document.scripts,找到第一个正确的“src”属性的脚本,并使用 data-attribute 或唯一 className 将其保存并标记为“visited”。
下次加载脚本时,请再次浏览 document.scripts,跳过任何已标记为 visited 的脚本。取第一个未访问的脚本实例。
这假设相同的脚本通常按照它们被加载的顺序执行,从 head 到 body,从上到下,从同步到异步。
(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
这就是你要的,@Gary99。 - LSOJ

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接