为啥用 document.write() 写 <script> 标签时要拆分它?

Posted

技术标签:

【中文标题】为啥用 document.write() 写 <script> 标签时要拆分它?【英文标题】:Why split the <script> tag when writing it with document.write()?为什么用 document.write() 写 <script> 标签时要拆分它? 【发布时间】:2010-09-19 03:53:00 【问题描述】:

为什么某些网站(或向客户提供 javascript 代码的广告商)采用一种技术,将 &lt;script&gt; 和/或 &lt;/script&gt; 标记拆分到 document.write() 调用中?

我注意到亚马逊也这样做,例如:

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

【问题讨论】:

【参考方案1】:

&lt;/script&gt; 必须被分解,否则它会过早结束封闭的&lt;script&gt;&lt;/script&gt; 块。实际上它应该在&lt;/ 之间分开,因为脚本块(根据SGML)应该是terminated by any end-tag open (ETAGO) sequence (i.e. &lt;/):

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

但实际上浏览器只在实际的 &lt;/script&gt; 关闭标签上解析 CDATA 脚本块。

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

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

【讨论】:

\// 的有效转义序列,那么为什么不直接使用它来代替&lt; 的那些字符串文字转义呢?例如。 document.write('&lt;script src=foo.js&gt;&lt;\/script&gt;');。此外,&lt;/script&gt; 不是唯一可以关闭&lt;script&gt; 元素的字符序列。更多信息:mathiasbynens.be/notes/etago @Mathias: &lt;\/script&gt; 在这种情况下很好,但它只适用于 HTML;在没有额外 CDATA 部分包装的 XHTML 中,它仍然是格式正确的错误。您还可以在内联事件处理程序属性中使用\x3C,其中&lt; 在 HTML 和 XHTML 中也无效,因此它具有更广泛的适用性:如果我选择一种易于自动化的方式来转义 JS 字符串中的敏感字符所有上下文的文字,这就是我想要的。 在 HTML 中,&lt; 可用于内联事件处理程序属性。 html5.validator.nu/… 你说对了 \x3C 的 XHTML 兼容性是对的,但由于 XHTML 不支持 document.write(或 innerHTML),我不明白这有什么关系。 @MathiasBynens—document.write 无关紧要,它只是一个例子。 OP 可以使用 innerHTML,它是关于从标记解析器中隐藏 &lt;/ 字符序列,无论它出现在哪里。只是大多数解析器在严格不应该的情况下在脚本元素中容忍它(但 HTML 解析器非常容忍)。你是对的,尽管&lt;\/ 在所有情况下都适用于 HTML。 我不认为转义开头 document.write('<script src="foo.js">\x3C/script>') 似乎在所有浏览器回到 IE6 中就足够了。 (我省略了 type 属性,因为它在 HTML5 中不是必需的,也不是任何浏览器所要求的。)【参考方案2】:

这是我在想要生成内联脚本标记(因此它立即执行)而不需要任何形式的转义时使用的另一种变体:

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

(注意:与网络上的大多数示例相反,我既没有在封闭标记上设置type="text/javascript",也没有在生成的标记上设置:没有浏览器没有将其作为默认值,因此它是多余的,但如果您不同意,也不会受到伤害)。

【讨论】:

好点re:类型。从 HTML5 开始,默认值被定义为“text/javascript”,所以它是一个无用的属性。 w3.org/html/wg/drafts/html/master/… 这个比公认的答案更好,因为这种变化实际上可以被缩小。这个 'x3C/script>' 会在缩小后变成 ''。【参考方案3】:

我认为是为了防止浏览器的 HTML 解析器解释 作为实际脚本的结束标记,但是我不认为使用 document.write 是一个好主意为了评估脚本块,为什么不使用 DOM...

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

【讨论】:

有必要防止解析器过早关闭脚本块...【参考方案4】:

Javascript 字符串中的 &lt;/script&gt; 被 HTML 解析器解释为结束标记,导致意外行为 (see example on JSFiddle)。

为避免这种情况,您可以将 javascript 放在 cmets 之间(这种编码方式很常见,早在浏览器对 Javascript 的支持很差时)。这会起作用(see example in 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>

【讨论】:

如果你想使用纯 JS,你仍然想使用 document.write ;) 对不起,我的意思是写 - 这需要 jQuery 库,对吧?当我使用document.body.append时,它抛出了一个错误,即document.body.append不是一个函数。 对不起,我的错误:我写的是append而不是appendChild。更正了答案。感谢您的关注! 这看起来比手动拆分字符串更通用。例如。如果字符串是由模板引擎插入的,则拆分是不可行的。【参考方案5】:

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 包含了一个条件加载来演示用例。希望这对某人有用!

【讨论】:

无需检测协议 - 无协议 URI 工作正常('//foo.com/bar.js' 等) 也不需要设置异步。它为所有动态创建的脚本标签设置。 救了我!使用 document.write( @dmp 除非从文件系统运行,协议为 file://

以上是关于为啥用 document.write() 写 <script> 标签时要拆分它?的主要内容,如果未能解决你的问题,请参考以下文章

为啥在 document.write 之前出现 alert? [复制]

为啥 script 标签的 document.write 会有时间差?为啥脚本标签执行延迟?

如果部分写入参与超时块,为啥由 document.write 创建的文档呈现方式不同?

JavaScript中4种document.write()输出展示

js常用函数

document.write 向文档中写内容,包括文本脚本元素之类的,但是它在什么时候执行不会覆盖当前页面内容尼?