为啥用 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 代码的广告商)采用一种技术,将 <script>
和/或 </script>
标记拆分到 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】:</script>
必须被分解,否则它会过早结束封闭的<script></script>
块。实际上它应该在<
和/
之间分开,因为脚本块(根据SGML)应该是terminated by any end-tag open (ETAGO) sequence (i.e. </
):
尽管 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>
【讨论】:
\/
是/
的有效转义序列,那么为什么不直接使用它来代替<
的那些字符串文字转义呢?例如。 document.write('<script src=foo.js><\/script>');
。此外,</script>
不是唯一可以关闭<script>
元素的字符序列。更多信息:mathiasbynens.be/notes/etago
@Mathias: <\/script>
在这种情况下很好,但它只适用于 HTML;在没有额外 CDATA 部分包装的 XHTML 中,它仍然是格式正确的错误。您还可以在内联事件处理程序属性中使用\x3C
,其中<
在 HTML 和 XHTML 中也无效,因此它具有更广泛的适用性:如果我选择一种易于自动化的方式来转义 JS 字符串中的敏感字符所有上下文的文字,这就是我想要的。
在 HTML 中,<
可用于内联事件处理程序属性。 html5.validator.nu/… 你说对了 \x3C
的 XHTML 兼容性是对的,但由于 XHTML 不支持 document.write
(或 innerHTML
),我不明白这有什么关系。
@MathiasBynens—document.write
无关紧要,它只是一个例子。 OP 可以使用 innerHTML,它是关于从标记解析器中隐藏 </
字符序列,无论它出现在哪里。只是大多数解析器在严格不应该的情况下在脚本元素中容忍它(但 HTML 解析器非常容忍)。你是对的,尽管<\/
在所有情况下都适用于 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 字符串中的 </script>
被 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 创建的文档呈现方式不同?