`document.write`是否适合查找与当前运行的脚本相关的元素?

时间:2013-01-19 03:14:59

标签: javascript html dom

如何找到与当前运行的脚本关联的脚本元素?我只对文档正文中的脚本感兴趣,而不是在头部(或其他地方)。

这是我到目前为止所做的,似乎工作正常。我有没有想到的任何陷阱?

function getCurrentScriptElement() {
    var placeholderId = 'placeholder-' + Math.random(), 
        script, placeholder;
    document.write('<br id="' + placeholderId + '">');
    placeholder = document.getElementById(placeholderId);
    script = placeholder.previousSibling;
    placeholder.parentNode.removeChild(placeholder);
    return script;
}

我想到的用例涉及需要在脚本标记出现的文档中的相同位置注入内容的脚本,因此延迟加载不会成为问题。换句话说,我只会在适当的地方调用此函数,它不会作为API或任何内容的一部分公开。

我也知道problemdocument.write和XHTML,在这种情况下(到目前为止)我并不担心。


我之前的尝试看起来像这样:

function getCurrentScriptElement() {
    var scripts = document.getElementsByTagName('script');
    return scripts[scripts.length - 1];
}

但是,正如我们在this question中看到的那样,如果将另一个脚本注入到文档中,这很容易失败。


我还在WHATWG spec中找到了document.currentScript。目前似乎没有得到广泛支持。 document.currentScript有一个有趣的垫片需要another route ...脚本触发错误,捕获它,检查堆栈跟踪以找到脚本的URL,然后找到匹配的脚本元素src属性。

这是一个聪明的解决方案,但它显然无法在IE中运行,如果多个脚本使用相同的URL,它将会中断。


理想情况下,我希望找到一个解决方案,它可以提供与顶级代码示例相同的结果(没有额外要求),但不依赖于document.write。这可能吗?如果没有,假设它被正确使用(仅在页面加载期间,而不是XHTML),这个代码是否相当安全?


修改:有关其他潜在用例的详情,请参阅this answer,并详细了解占位符使用br元素的原因。

4 个答案:

答案 0 :(得分:2)

OP对于棘手的问题有很好的解决方案。然而,这里有一些改进它的小建议。

该函数应该在可用时使用document.currentScript。无论情况如何,它都运作良好。适用于FireFox + 4,Chrome +29和Opera +16。

使用IE 6-10,script.readyState可用于模拟document.currentScript。无论情况如何,它似乎都能很好地运作。

为避免出现问题,该函数应在document.readyState ==“complete”时退出,或者甚至在早期的“交互”阶段退出,具体取决于代码。 “完成”时,document.currentScript似乎停止工作。但是,document.write在“interactive”处停止,即,使用延迟加载的脚本,查找脚本的标记写入方法将失败。

在使用document.write()之前,应检查脚本async属性。标记为异步的脚本将从文档中分离出来,document.write()方法将无法按预期工作。

OP的代码写了&lt; p&gt;标签,但样式标签似乎在身体和头部的脚本中更好地工作......并且没有副作用。

随机元素标记ID似乎没有用处,因为元素已被删除。使用重复使用的GUID常量可能会更好。

<强>更新

我已经更新了下面的代码示例,以说明在不编写代码的情况下执行此操作的方法。它似乎适用于Chrome,FireFox,Opera和IE 5-10。无论注入的脚本和脚本属性(延迟和异步)如何,都返回正确的值。困难的部分是如何处理IE + 11? Microsoft在IE11中删除了script.readyState,但没有提供替代方案。一种解决方法是使用“MutationObserver”来展望脚本。在IE中,这些事件事件每次都在脚本执行之前发生(而不是之后发生的onload)。除非有外部加载的脚本设置了延迟或异步属性,否则这种方法很有效。异步脚本特别困难,因为它们可以随时执行,而不是总是以相同的顺序执行(我的观察)。

无论如何,我想我会发布这段代码,希望它可以提供一个起点并帮助其他人。

参考文献:

https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver

http://msdn.microsoft.com/en-us/library/ie/dn265034(v=vs.85).aspx

CODE SNIPPET

    var currentScript = (function() {

        // PRIVATE
        var observer, current, nodes;

        // IE+11 - untested with other browsers.
        if (!document.currentScript && typeof MutationObserver=='function') {
            observer = new MutationObserver(function(mutations) {
                if (document.readyState=='complete') observer.disconnect();
                mutations.forEach(function(mutation) {
                    nodes = mutation.addedNodes;
                    if (nodes.length && nodes[0].nodeName=='SCRIPT') {
                        current = nodes[0];
                    }
                });
            });
            observer.observe(window.document, {childList: true, subtree: true});
        }

        // PUBLIC
        return function() {

            if (document.readyState=='complete') return;

            // CHROME+29, FIREFOX+4, OPERA+16
            if (document.currentScript) return document.currentScript;

            var i, s=document.getElementsByTagName('SCRIPT');

            // MSIE+5-10
            if (s[0].readyState) 
                for(i=0; i<s.length; i++)
                    if (s[i].readyState=='interactive') return s[i];

            // IE+11
            if (current) return current;

            // BEST GUESS
            return s[s.length-1];

        }

    })()

答案 1 :(得分:0)

如果您的脚本不是内联的(例如,它是通过'&lt; script src&gt;'加载的,则在try块中抛出一个错误,然后在catch块中检查它的堆栈跟踪。您将能够确定文件名然后你可以使用'querySelector('[src =“'+ filename +'”]')'找到标签,然后在它或它的兄弟之前插入你的内容。

答案 2 :(得分:-1)

假设您的脚本没有延期,您可以这样做:

var allScripts = document.getElementsByTagName("script"),
    currentScript = allScripts[allScripts.length-1];

答案 3 :(得分:-2)

另一个想法是:根据w3schools <script> tag supports global HTML attributes

使用任何全局属性(idclasstitle)您可以区分使用的脚本标记,但在这种情况下您必须预先定义{{ 1}}和id