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

How may I reference the script tag that loaded the currently-executing script?

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

情况就是这样。我有一个"master"脚本被加载到页面的高位置,第一件事是在head标签下。

1
2
3
4
5
<!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">

脚本"js"中有一个脚本,它需要能够按需加载其他脚本。正常的方法对我来说不太有效,因为我需要添加新的脚本而不引用头标签,因为头元素还没有完成渲染:

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

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

1
2
3
<script type="text/javascript" src="scripts.js">
loaded by scripts.js--><script type="text/javascript" src="new_script1.js">
loaded by scripts.js --><script type="text/javascript" src="new_script2.js">


如何获取当前脚本元素:1。使用document.currentScript

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

1
var me = document.currentScript;

效益

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

问题

  • 不能在旧的浏览器和IE中使用。

2。按ID选择脚本

给脚本一个id属性可以让您很容易地从使用document.getElementById()的内部按id选择它。

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

效益

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

问题

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

三。使用data-*属性选择脚本

给脚本一个data-*属性可以让您很容易地从内部选择它。

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

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

效益

  • 简单明了。
  • 使用异步脚本(deferasync)
  • 动态插入脚本的工作

问题

  • 需要向脚本标签添加自定义属性
  • HTML5和EDCOX1 13不兼容在所有浏览器中
  • 使用EDOCX1·8属性不太广泛支持
  • 用EDCX1和8的边缘情况来获得EDOCX1·2Ω。
  • 如果另一个元素在页面上具有相同的数据属性和值,则可能会混淆。

4。用SRC选择脚本

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

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

嵌入:JS:

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

效益

  • 可靠的
  • 使用异步脚本(deferasync)
  • 使用动态插入的脚本
  • 不需要自定义属性或ID

问题

  • 不适用于本地脚本
  • 会在不同的环境中造成问题,如开发和生产
  • 静态和脆弱。更改脚本文件的位置需要修改脚本
  • 比使用id属性得到的支持少
  • 如果两次加载同一个脚本,将导致问题

5。循环所有脚本以查找所需的脚本

我们还可以循环遍历每个脚本元素,并分别检查每个脚本元素以选择所需的元素:

1
2
3
4
5
6
7
var me = null;
var scripts = document.getElementsByTagName("script")
for (var i = 0; i < scripts.length; ++i) {
    if( isMe(scripts[i])){
      me = scripts[i];
    }
}

这使得我们可以在不支持querySelector()和属性的旧浏览器中使用前面的两种技术。例如:

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

这继承了所采用的任何方法的好处和问题,但不依赖于querySelector(),因此可以在旧浏览器中使用。

6。获取上次执行的脚本

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

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

效益

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

问题

  • 不适用于异步脚本(deferasync)
  • 不适用于动态插入的脚本


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

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


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


只有当脚本没有"延迟"或"异步"属性时,脚本才被顺序执行。知道脚本标签中可能的ID/SRC/title属性之一在这些情况下也可以工作。所以格雷戈和贾斯廷的建议都是正确的。

在WHOWG列表上已经有一个关于EDOCX1 1的建议。

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

编辑:没有人提到"document.scripts"集合,但我相信以下内容可能是获取当前运行脚本的一个很好的跨浏览器选择:

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


这里有一个polyfill,它利用了document.currentScript,如果它存在,就返回到按ID查找脚本。

1
2
3
4
5
6
<script id="uniqueScriptId">
    (function () {
        var thisScript = document.CurrentScript || document.getElementByID('uniqueScriptId');

        // your code referencing thisScript here
    ());

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

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


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

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


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

1
var thisScript = document.currentScript;

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

1
var url = thisScript.src

处理异步和延迟脚本的方法是利用onload处理程序-为所有脚本标记设置一个onload处理程序,第一个执行的应该是您自己的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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.scripts,找到具有正确"src"属性的第一个脚本,然后保存它,并用数据属性或唯一的类名将其标记为"已访问"。

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
(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算法,其中节点可能标记为"已访问",以防止重新访问。


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

  • 在脚本块中放入一些随机的唯一字符串(每个脚本块中必须是唯一的/不同的)
  • 迭代document.getElementsByTagname("script")的结果,从每个内容中查找唯一的字符串(从InnerText/TextContent属性获取)。
  • 示例(abcde345678是唯一ID):

    1
    2
    3
    4
    5
    <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

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

    原始HTML页面示例:

    1
    2
    3
    4
    5
    <!doctype html>
    <html><head>
    <script src="script.js">
    <script src="otherscript.js">
    <body>anything</body></html>

    script.js的内容:

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

    渲染后,DOM结构将变为:

    1
    2
    3
    4
    5
    HEAD
      SCRIPT script.js
      SCRIPT inserted.js
      SCRIPT otherscript.js
    BODY


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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
      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');


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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    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);

    我有这个,它在FF3,IE6&7工作。按需加载脚本中的方法在页面加载完成之前不可用,但这仍然非常有用。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    //handle on-demand loading of javascripts
    makescript = function(url){
        var v = document.createElement('script');
        v.src=url;
        v.type='text/javascript';

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