使用JavaScript添加<script>和<link>元素的优缺点是什么?

10

最近我看到一些HTML代码,在其<head>中只有单个<script>元素...

<head>
    <title>Example</title>
    <script src="script.js" type="text/javascript"></script>
    <link href="plain.css" type="text/css" rel="stylesheet" />
</head>

这个 script.js 然后使用 document.write(...) 添加任何其他必要的 <script> 元素和 <link> 元素到文档中:(或者它可以使用 document.createElement(...) 等方法)

document.write("<link href=\"javascript-enabled.css\" type=\"text/css\" rel=\"styleshet\" />");
document.write("<script src=\"https://ajax.googleapis.com/ajax/libs/jquery/1.5.1/jquery.min.js\" type=\"text/javascript\"></script>");
document.write("<script src=\"https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.10/jquery-ui.min.js\" type=\"text/javascript\"></script>");
document.write("<link href=\"http://ajax.googleapis.com/ajax/libs/jqueryui/1.7.0/themes/trontastic/jquery-ui.css\" type=\"text/css\" rel=\"stylesheet\" />")
document.write("<script src=\"validation.js\" type=\"text/css\"></script>")
请注意,文档的中有一个plain.css CSS文件,而script.js只是添加JS启用的用户代理将使用的所有CSS和JavaScript。 这种技术的一些优缺点是什么?
9个回答

12

document.write的阻塞性质

document.write会暂停浏览器页面上正在处理的所有内容(包括解析)。 由于这种阻止行为,强烈建议避免使用它。 浏览器无法知道您将在HTML文本流中输入什么,或者写入是否会完全破坏DOM树上的一切,因此它必须停止直到您完成为止。

从本质上讲,以这种方式加载脚本将迫使浏览器停止解析HTML。 如果您的脚本是内联的,那么浏览器在继续执行之前也会执行这些脚本。 因此,顺便提一下,始终建议您推迟加载脚本,直到您的页面被解析并向用户显示出合理的UI。

如果您的脚本从"src"属性中单独文件中加载,则可能无法在所有浏览器上一致地执行脚本。

失去浏览器速度优化和可预测性

以这种方式,您将失去现代浏览器进行的许多性能优化。 此外,脚本的执行时间可能是不可预测的。

例如,某些浏览器将在“写入”脚本后立即执行这些脚本。 在这种情况下,您会失去脚本的并行下载能力(因为浏览器直到下载和执行第一个脚本标签之前才看到第二个脚本标签)。 您将失去脚本、样式表和其他资源的并行下载(许多浏览器可以同时下载资源、样式表和脚本)。

某些浏览器会将脚本推迟到最后才执行。

由于document.write的阻塞行为,当HTML解析正在进行时以及在执行编写的脚本时,在某些情况下,浏览器无法继续解析HTML,因此您的页面显示速度会大大降低。

换句话说,你的网站就像在没有优化的老式浏览器上加载一样慢了。

为什么有人会这样做?

你可能想使用这样的东西通常是出于可维护性的考虑。例如,你可能拥有一个包含数千个页面的大型网站,每个页面都会加载相同的脚本和样式表。但是,当你添加一个脚本文件时,你不希望编辑成千上万个HTML文件来添加脚本标签。尤其是在加载JavaScript库(例如Dojo或jQuery)时,这会特别麻烦 -- 当你升级到下一个版本时,你必须更改每个HTML页面。

问题在于JavaScript没有 @include 或 @import 语句来包含其他文件。

一些解决方案

解决这个问题的方法可能不是通过 document.write 注入脚本,而是:

  1. 在样式表中使用 @import 指令
  2. 使用服务器脚本语言(例如PHP)管理你的“母版页”并生成所有其他页面(但是,如果你不能使用它并且必须单独维护许多HTML页面,则这不是一个解决方案)
  3. 避免使用 document.write,而是通过XHR加载JavaScript文件,然后eval()它们 -- 这可能存在安全问题
  4. 使用一个具有模块加载功能的JavaScript库(例如Dojo),以便你可以保留一个主JS文件,该文件加载其他文件。但你将无法避免更新库文件的版本号...

使用eval存在一些问题,而且这不仅仅是安全问题。在我看来,最大的问题是通过XHR和eval加载的脚本不会显示在Firebug可用脚本列表中。这意味着调试此代码将非常困难。但是,使用document.write()则不会出现这种情况。顺便说一下,Dojo的包管理使用了eval。 - jordancpaul
这里的eval难道不只是评估生成实际HTML脚本标记的代码,而不是评估文件内容本身吗?我认为说这会阻止并行下载可能会误导人,但我可能错了...有引用吗? - Wesley Murch
此外,您建议使用@import,但它会阻止并行下载 - Wesley Murch
@Madmartigan,我认为您不需要附加脚本标记。在加载DOM后,只需XHR脚本文本并在那一点上进行评估即可。 - Stephen Chung
@Madmartigan,@import嵌入CSS文件中的内容只有在该CSS文件下载完成后才会被下载。但是,如果该CSS文件很短(仅包含@import指令),则不应该对@import的文件产生太大的延迟。我相信多个@import可以并行下载。当然,CSS下载不会阻止脚本的下载。 - Stephen Chung
显示剩余2条评论

8

一个主要的缺点是浏览器不兼容。并非所有浏览器都能正确获取和整合资源到DOM中,因此使用这种方法是有风险的。在样式表方面,这更为真实。

另一个问题是可维护性。在客户端拼接和编写字符串以添加DOM元素可能会成为维护的噩梦。最好使用DOM方法,例如createElement来语义化创建元素。

一个明显的优点是它使条件使用资源变得更加容易。您可以有逻辑确定要加载哪些资源,从而减少页面的带宽消耗和总体处理时间。我建议使用库调用,如jQuery $.getScript()来加载脚本,而不是使用document.write。这种方法更清晰,还允许您在请求完成或失败时执行代码。


你能详细说明一下浏览器的不兼容性吗?也许可以提到一个会因这种问题而出现错误的浏览器? - Richard JP Le Guen
在你的第二段中,你说的“语义化创建元素”是什么意思?我觉得这可能不是“语义化”的正确用法。 - Richard JP Le Guen
以下是有关使用不同技术注入脚本的性能结果的良好文章:http://brunosouza.posterous.com/high-performance-web-sites-loading-scripts-wi坦率地说,我已经很久没有使用document.write(),因为它是一种非常糟糕的编码实践,所以我需要进行一些研究来描述具体的情况。对不起。我正确地使用了“语义化”。"createElement"清晰地传达了在DOM中“创建元素”的意图。 - Nik Kalyani
@techbubble - 哦,现在我明白了。我一开始以为你是指语义化HTML;现在我明白你是指代码上的清晰度。 - Richard JP Le Guen
然而,getScript() 存在一些问题。首先,它通过 ajax 加载 javascript,这意味着无法进行跨站脚本攻击 - 也就是说,无法加载不来自您网站的 javascript。如果您想包含描述中列出的 https://ajax.googleapis.com,这将是一个问题。其次,我非常确定包含的脚本不会显示在 firebugs 的页面脚本列表中,这使得调试非常具有挑战性。 - jordancpaul
实际上,那并不完全准确。您可以使用getScript从任何服务器加载脚本。此外,这些脚本在Firebug中显示正常。 - Nik Kalyani

8

好的,我想我也可以在这方面提供一些帮助...

如果您查看谷歌的闭包库base.js,您会发现document.write被用于他们的writeScriptTag_()函数。这是'closure'提供的依赖管理系统的重要组成部分,在创建复杂的、多文件的、基于库的javascript应用程序时具有巨大的优势——它让文件/代码的前提条件决定了加载顺序。我们目前使用这种技术,并且一直没有遇到任何问题。说实话,我们在IE 6/7/8、FF3/2、Safari 4/5和Chrome最新版本上进行定期测试,没有遇到过浏览器兼容性的问题。

到目前为止,我们唯一遇到的缺点是,追踪由于重复加载资源或未能加载资源而导致的问题可能比较困难。由于加载资源的行为是程序化的,因此受程序错误的影响,而与直接将标签添加到HTML不同,它很难看出确切的加载顺序。然而,通过使用具有某种形式的依赖管理系统的库,如closure或dojo,可以在很大程度上克服这个问题。

编辑:我已经做了一些评论,但我认为在我的回答中总结最好:

  1. 通过ajax加载意味着没有跨站脚本——即不会加载不来自您网站的javascript。如果您想包含描述中列出的https://ajax.googleapis.com,这将是一个问题。
  2. Eval'd脚本不会显示在JavaScript调试器的页面脚本列表中,使得调试相当具有挑战性。最近发布的firebug将向您显示eval'd代码,但文件名丢失,使设置断点的行为变得繁琐。据我所知,Webkit JavaScript控制台和IE8开发人员工具不会显示eval'd脚本。

+1 - 我喜欢这个答案!您列举了使用此技术的JS库的具体示例,并命名了它已经在哪些浏览器上工作。谢谢。 - Richard JP Le Guen
没问题,很高兴能帮忙!一定要阅读我的其他评论——使用ajax加载脚本存在一些问题... - jordancpaul

4

它的优点在于您不需要在每个HTML文件中重复脚本引用。缺点是浏览器必须在加载其他文件之前获取并执行主JavaScript文件。


你能详细说明一下这个缺点吗?我不太明白它为什么一定是个缺点。 - Richard JP Le Guen
3
这意味着页面加载会变慢。浏览器首先加载HTML页面,读取它,然后读取主脚本,接着再读取所有其他脚本。如果这些脚本直接在HTML文件中引用,浏览器可以同时获取它们(特别是因为其中一些来自外部服务器)。 - Alexander Gessler
在这种情况下,获取和加载“主JavaScript文件”非常简单,因为它只是输出一些<script>和<link>标签,应该会非常快速。之后,浏览器应该看到正常的<script>标签,它将并行下载,就像如果有“硬编码”的脚本标签一样。 - Wesley Murch
没错 - 但这仍然会导致并行性降低,这才是最重要的。 - Alexander Gessler

2

我想到的一个优点是,如果您在多个页面上使用这些脚本,您只需要记得包含一个脚本,就可以节省一些空间。


2
在Google PageSpeed中,他们强烈反对使用这种技术,因为它会使事情变慢。除了在所有其他脚本之前按顺序加载您的script.js之外,还有另一个问题: 现代浏览器使用推测式解析器来更有效地发现外部资源[...] 因此,使用JavaScript的document.write()来获取外部资源会使得推测式解析器无法发现这些资源,这可能会延迟这些资源的下载、解析和渲染。

2
这可能是由SEO公司写成的建议,以使头部元素变短,因此独特的内容更靠近文档顶部,也创造了更高的文本-HTML比率。虽然总体来说这听起来不是一个很好的做法;如果发现减少头部元素大小是绝对必要的,更好的方法可能是将JavaScript压缩为单个.js文件并将CSS压缩为单个.css文件,虽然这样会增加维护时间。

1
一个很大的缺点是将 script 标签添加到 head 中会暂停文档的处理,直到这些脚本被完全下载、解析和执行(因为浏览器认为它们可能使用 document.write)。这会影响响应速度。
现在建议您将脚本标签放在 </body> 之前。当然,这并非总是可能的,但如果您正在使用无侵入式 JavaScript(正如您应该做的那样),所有脚本都可以重新定位到文档的末尾。
HTML5 提供了 async 属性,建议浏览器仅在主文档加载完成后执行脚本。这是许多浏览器中插入脚本的行为,但并非所有浏览器都支持。
我强烈反对使用 document.write。即使没有使用它,这也会导致向服务器发送额外的请求。(我们喜欢通过 css sprites 来最小化请求次数。)

是的,正如其他人之前提到的那样,如果禁用脚本,则页面将显示为没有 CSS(这可能使其无法使用)。


0
  1. 如果 JavaScript 被禁用 - <script><link> 元素将不会被添加。

  2. 如果您将 JavaScript 初始化函数放置在页面底部(这是一个好习惯),并使用 JavaScript 链接 CSS,可能会导致 CSS 加载之前出现一些延迟(破碎的布局将会短暂可见)。


关于第一点 - 这就是重点所在!只有在启用JS时才需要的任何JavaScript和CSS都不会出现在文档中,除非JS已启用。 - Richard JP Le Guen
关于第二点,在问题中JS明确在文档的<head>中。 - Richard JP Le Guen
如果JS库包含自我初始化(这是一种不好的做法),那么第二点就不成立了。 - Scherbius.com
关于第一点 - 我的意思是:没有启用JS的用户也不会链接CSS。 - Scherbius.com
是的,它们将具有CSS链接:它们将具有<link href="plain.css" type="text/css" rel="stylesheet" />,这不是通过JavaScript添加的。如果它们没有JS,则不需要任何其他内容。您所说的“self-init”是什么意思? - Richard JP Le Guen

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