为什么在使用document.write()时要分割<script>标签?

284
为什么有些网站(或者为客户提供javascript代码的广告商)在document.write()调用中分割<script>和/或</script>标签?我注意到亚马逊也这样做,例如:
<script type='text/javascript'>
  if (typeof window['jQuery'] == 'undefined') document.write('<scr'+'ipt type="text/javascript" src="http://z-ecx.images-amazon.com/images/G/01/javascripts/lib/jquery/jquery-1.2.6.pack._V265113567_.js"></sc'+'ript>');
</script>
5个回答

384

</script>必须被分开,否则它会过早地结束包含它的<script></script>块。实际上,它应该在</之间拆分,因为根据SGML标准,脚本块应该由任何结束标记打开(ETAGO)序列(即</终止:

尽管STYLE和SCRIPT元素使用CDATA作为其数据模型,但对于这些元素,用户代理必须以不同的方式处理CDATA。标记和实体必须被视为原始文本并按原样传递给应用程序。字符序列“</”(结束标记打开定界符)的第一次出现被视为终止元素内容的末尾。在有效文档中,这将是元素的结束标记。

然而,在实践中,浏览器只有在实际的</script>闭合标记上结束解析CDATA脚本块。

在XHTML中,没有这样的特殊处理脚本块,因此其中的任何<(或&)字符都必须像在任何其他元素中一样&escaped;。但是,解析XHTML作为老式HTML的浏览器将会感到困惑。有涉及CDATA块的解决方法,但最简单的方法是避免未经转义地使用这些字符。从脚本编写适用于任何类型解析器的脚本元素的更好方法是:

<script type="text/javascript">
    document.write('\x3Cscript type="text/javascript" src="foo.js">\x3C/script>');
</script>

35
\/ 是代表 / 的有效转义序列,那为什么不直接使用它来替代针对 < 的字符串字面值转义呢?例如 document.write('<script src=foo.js><\/script>'); 。此外,</script> 并不是唯一一个可以关闭 <script> 元素的字符序列。更多信息请参考:http://mathiasbynens.be/notes/etago - Mathias Bynens
11
@Mathias:在这种情况下,<\/script> 在 HTML 中是可以的,但在没有额外 CDATA 包装的 XHTML 中,它仍然是一个格式正确性错误。此外,在内联事件处理程序属性中,您也可以使用 \x3C,其中 < 在 HTML 和 XHTML 中都是无效的,因此它具有更广泛的适用性:如果我选择一种易于自动化的方法来转义 JS 字符串文字中的敏感字符以适用于所有上下文,那么我会选择这个。 - bobince
3
在HTML中, < 可以用于行内事件处理程序属性。 http://html5.validator.nu/?doc=data%3Atext%2Fhtml%3Bcharset%3Dutf-8%2C%3C!doctype+html%3E%3Ctitle%3E%3C%2Ftitle%3E%3Cp%2520contenteditable%2520oninput%3D%22alert('%3Clol%3E')%22%3E 你对 \x3C 的XHTML兼容性也是正确的,但由于XHTML无论如何都不支持 document.write (或 innerHTML ),所以我不知道这与相关性。 - Mathias Bynens
2
@MathiasBynens — document.write是无关紧要的,它只是一个例子。OP本来可以使用innerHTML,这与隐藏</字符序列不相关,无论它出现在哪里的标记解析器中。只是大多数解析器会在脚本元素内容忍它,但严格来说它们不应该(但HTML解析器非常容忍)。不过您是正确的,对于HTML,<\/在所有情况下都足够了。 - RobG
3
我认为转义开头的 < 不是必需的...... 在所有浏览器中,包括 IE6,document.write('<script src="foo.js">\x3C/script>') 看起来都是足够的。(我省略了 type 属性,因为它在 HTML5 中不是必需的,并且任何浏览器也不会强制执行该属性。) - Matt Browne
显示剩余3条评论

40

以下是我在需要内联生成脚本标签(以使其立即执行)且不需要任何转义的情况下使用的另一种变体:

<script>
    var script = document.createElement('script');
    script.src = '/path/to/script.js';
    document.write(script.outerHTML);
</script>

(注意:与大多数网上的例子相反,我没有在包含标签或生成的标签上设置type="text/javascript":没有浏览器不将其默认设置为此选项,因此它是冗余的。但是如果您不同意,也不会有影响。)


1
关于类型的观点很好。从HTML5开始,默认定义为"text/javascript",因此它是一个无用的属性。http://www.w3.org/html/wg/drafts/html/master/scripting-1.html#attr-script-type - Luke
10
这个答案比被采纳的答案更好,因为这个变体实际上可以被压缩。在压缩后,'x3C/script>' 会变成 '</script>'。 - Zoltan Kochan

21

我认为这是为了防止浏览器的HTML解析器将<script>和主要的</script>解释为实际脚本的结束标签,但我认为使用document.write来评估脚本块并不是一个出色的想法,何不使用DOM...

var newScript = document.createElement("script");
...

4
需要防止解析器过早关闭脚本块... - roenving

12
在 JavaScript 字符串字面量中的 </script> 会被 HTML 解析器解释为结束标签,导致出现意外行为(见 JSFiddle 上的示例)。
为了避免这种情况,您可以将 JavaScript 代码放在注释之间(在浏览器支持 JavaScript 很差的时代,这种编码风格是常见的)。这样就能正常工作(请参见 JSFiddle 上的示例)。
<script type="text/javascript">
    <!--
    if (jQuery === undefined) {
        document.write('<script type="text/javascript" src="http://z-ecx.images-amazon.com/images/G/01/javascripts/lib/jquery/jquery-1.2.6.pack._V265113567_.js"></script>');
    }
    // -->
</script>

......但是说实话,使用document.write并不是我认为最佳实践的做法。为什么不直接操作DOM呢?

<script type="text/javascript">
    <!--
    if (jQuery === undefined) {
        var script = document.createElement('script');
        script.setAttribute('type', 'text/javascript');
        script.setAttribute('src', 'http://z-ecx.images-amazon.com/images/G/01/javascripts/lib/jquery/jquery-1.2.6.pack._V265113567_.js');
        document.body.appendChild(script);
    }
    // -->
</script>

1
如果你想使用纯JS,你仍然需要使用document.write ;) - Nirav Zaveri
抱歉,我的意思是需要使用jQuery库,对吗?当我使用document.body.append时,它会抛出一个错误,指出document.body.append不是一个函数。 - Nirav Zaveri
1
抱歉,我的错误:我写成了 append 而不是 appendChild。已更正答案。感谢您的注意! - Mathieu Rodic
1
这看起来比手动拆分字符串要通用得多。例如,如果字符串是由模板引擎插入的,则拆分是不可行的。 - w1th0utnam3

9

Bobince发布的解决方案对我来说完全有效。我还想为未来的访问者提供一种替代方法:

if (typeof(jQuery) == 'undefined') {
    (function() {
        var sct = document.createElement('script');
        sct.src = ('https:' == document.location.protocol ? 'https' : 'http') +
          '://ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.min.js';
        sct.type = 'text/javascript';
        sct.async = 'true';
        var domel = document.getElementsByTagName('script')[0];
        domel.parentNode.insertBefore(sct, domel);
    })();
}

在这个例子中,我包含了一个条件加载jQuery的示例来演示使用情况。希望对某些人有用!

3
不需要检测协议 - 无协议URI可以正常工作(如'//foo.com/bar.js'等)。 - danp
3
不需要设置async。对于所有动态创建的脚本标签来说,它默认为开启状态。 - Noishe
救了我!当我使用document.write(<script>)时,我的网站显示空白屏幕。这个方法有效。 - Tomas Gonzalez
除非从文件系统运行,否则协议将为file://。@dmp - mplungjan

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