有哪些替代document.write的方法?

81
在教程中,我学习了如何使用document.write。现在我明白了,很多人不赞成这样做。我尝试过print(),但它直接发送到打印机。

那么,我应该使用哪些替代方法?为什么不应该使用document.write?w3schools和MDN都使用document.write


3
print() 解析为 window.print() - alex
2
JavaScript的print()和PHP的print()不同。JS会直接将页面发送到打印机,而PHP只是输出代码。 - Ian Hazzard
12个回答

98
你的HTML被替换的原因是因为一个邪恶的JavaScript函数:document.write()。这绝对是“不合适”的。如果在页面加载时使用它,它只能与网页一起工作;如果在运行时使用它,则会用输入替换整个文档。如果你将其应用于严格的XHTML结构,则甚至不是有效的代码。

问题:

document.write向文档流中写入内容。在已关闭(或已加载)的文档上调用document.write会自动调用document.open,这将清空文档。

-- 摘自MDN的引用

document.write()有两个助手,document.open()document.close()。当HTML文档加载时,文档是“打开”的。当文档加载完成时,文档“关闭”。此时使用document.write()将会擦除整个(已关闭的)HTML文档,并用一个新的(打开的)文档替换它。这意味着您的网页已经擦除自己并开始编写一个新页面 - 从头开始。

我相信document.write()也会导致浏览器性能下降(如果我错了,请纠正我)。


一个例子:

这个例子在页面加载后向HTML文档中输出内容。当你按下“exterminate”按钮时,观察document.write()的邪恶力量清除整个文档:

I am an ordinary HTML page.  I am innocent, and purely for informational purposes. Please do not <input type="button" onclick="document.write('This HTML page has been succesfully exterminated.')" value="exterminate"/>
me!


替代方案:

  • .innerHTML 这是一个很好的替代方案,但必须将此属性附加到要放置文本的元素上。

例子:document.getElementById('output1').innerHTML = 'Some text!';

  • .createTextNode()W3C推荐的替代方案。

例子:var para = document.createElement('p'); para.appendChild(document.createTextNode('Hello, '));

注意:已知这会导致一些性能下降(比.innerHTML慢)。我建议使用.innerHTML


使用.innerHTML替代方案的示例:

I am an ordinary HTML page. 
I am innocent, and purely for informational purposes. 
Please do not 
<input type="button" onclick="document.getElementById('output1').innerHTML = 'There was an error exterminating this page. Please replace <code>.innerHTML</code> with <code>document.write()</code> to complete extermination.';" value="exterminate"/>
 me!
<p id="output1"></p>


1
那么恶意行为只有在文档关闭后才开始?除了不时髦之外,使用document.write是否存在任何缺点?是否存在关于文档何时关闭的实际不确定性? - Bob Stein
@BobStein-VisiBone,是的,document.write()可以在页面加载期间调用,而浏览器正在解析页面。一旦页面解析/加载完成,调用此函数也将调用document.open(),它会清除页面并从头开始。我想不到任何理由不在页面加载期间使用该函数,除了与您在任何其他时间添加内容到页面的方式保持一致。文档在浏览器完成解析文档后立即关闭。 - Ian Hazzard
document.write() 似乎仍然是 cdn local fallback 的最佳解决方案。我还没有尝试过 getElementById/innerHTML,但是 createElement/insertBefore 解决方案证明是 非同步的 - Bob Stein
2
@Godisgood,document.write不可能存在性能问题,因为它是浏览器在加载初始HTTP响应时使用的完全相同的函数。 - Pacerier
@Pacerier 是的,但我的意思是在页面加载后使用 document.write() 会更慢,因为每次调用该函数时都必须清除文档,然后重新打开它。.innerHTML 不需要这样做,所以我很确定它实际上更快。我会寻找支持这一观点的来源。我相信如果在页面已经打开的情况下使用 document.write() 是很快的。 - Ian Hazzard

30

这里是应该取代 document.write 的代码:

document.write=function(s){
    var scripts = document.getElementsByTagName('script');
    var lastScript = scripts[scripts.length-1];
    lastScript.insertAdjacentHTML("beforebegin", s);
}

2
如果页面上没有脚本,这样做不会起作用,对吗? - Noyo
1
好的,无论您将该函数放在哪个页面上,至少会有一个页面。但是如果唯一的脚本位于<head></head>中怎么办?他提到的关键是insertAdjacentHTML函数。 - Lonnie Best
1
@Noyo - 通常你会在页面上的<script>标签内使用document.write,就像Github GISTs一样:https://gist.github.com/adriank/7436248.js - Adrian Kalbarczyk
字符串不应该放置在 'beforebegin',而是应该放置在 'afterend',以匹配本地的 document.write。虽然这并不重要。 - Gust van de Wal
好的,我撤回上一个评论。看了Sorin的答案的第一个修订版之后,我意识到将东西添加到DOM中的顺序可能比相对于script标签的位置更重要。 - Gust van de Wal
显示剩余4条评论

19
您可以将 insertAdjacentHTML 方法和 document.currentScript 属性结合起来使用。
Element 接口的 insertAdjacentHTML() 方法会将指定的文本解析为 HTML 或 XML,并将生成的节点插入到 DOM 树中的指定位置
  • 'beforebegin':在元素本身之前。
  • 'afterbegin':在元素内部,其第一个子元素之前。
  • 'beforeend':在元素内部,其最后一个子元素之后。
  • 'afterend':在元素本身之后。

document.currentScript属性返回当前正在处理的脚本的<script>元素。最佳位置应该在beforebegin之前——新的HTML将被插入到<script>本身之前。为了匹配document.write的本地行为,一个人应该将文本放置在afterend之后,但是这样连续调用函数产生的节点不会按照您调用它们的顺序(像document.write一样)放置,而是反向放置。您的HTML出现的顺序可能比它们相对于<script>标签的位置更重要,因此使用beforebegin

document.currentScript.insertAdjacentHTML(
  'beforebegin', 
  'This is a document.write alternative'
)


1
尽管这可能回答了问题,但请提供任何解释以确保人们理解这里发生的事情。 - Randy
由于这里的评论,我的问题得到了解决。 我在这里解释了问题所在以及对我有用的解决方法: https://dev59.com/rE3Sa4cB1Zd3GeqPw6e8#64610187 - K. Shaikh

14
作为对 document.write 的推荐替代方案,您可以使用 DOM操作 直接查询并向 DOM 添加节点元素。

12
如果回答能解释为什么document.write有问题,那么这将是一个更好的答案,就像这个答案所做的那样。 - Quentin

12

在这里留个备注,虽然使用document.write由于性能问题(同步DOM注入和评估)而备受谴责,但如果您正在使用document.write来按需注入脚本标记,则实际上没有1:1的替代方法

有很多避免这样做的好方法(例如像RequireJS这样管理您的依赖关系链的脚本加载器),但它们更具侵入性,因此最好在整个站点/应用程序中使用。


8
我不明白为什么使用 document.write 会有问题。如果您在 onload 事件触发之前使用它来构建结构化数据的元素,那么它就是适当的工具。使用 insertAdjacentHTML 或在构建 DOM 后显式添加节点没有性能优势。我刚刚用一种旧脚本测试了三种不同方式,该脚本曾用于安排银行的4个调制解调器提供24/7服务。

在完成此脚本时,它会创建超过3000个 DOM 节点,大部分是表格单元格。在运行 Firefox 的 Vista 操作系统上运行7年的 PC 上,从本地12kb源文件和三个重复使用约2000次的1px GIF使用 document.write,这个小练习只需要不到2秒钟。页面完全形成,准备好处理事件。

使用 insertAdjacentHTML 不是直接替代品,因为浏览器会关闭脚本需要保持打开的标签,并且最终创建一个混乱的页面需要 两倍的时间。将所有组件写入字符串,然后将其传递给 insertAdjacentHTML 需要更长的时间,但至少可以按设计获取页面。其他选项(例如手动逐个重新构建 DOM)非常荒谬,我甚至不会去那里。

有时候使用 document.write 是必要的。它是 JavaScript 中最古老的方法之一并不是缺点,而是优点 - 它是高度优化的代码,完全做到了旨在做的事情,并且自从诞生以来一直如此。

知道有可替代的后加载方法很好,但必须理解这些方法完全用于修改已创建和分配给其内存的 DOM。如果您的脚本旨在编写 HTML,然后浏览器首先创建 DOM,则使用这些方法固有更多的资源消耗。

只需编写并让浏览器和解释器完成工作。这就是它们的存在目的。

PS:我刚刚在 body 标签中使用了一个 onload 参数,即使此时文档仍处于 open 状态,document.write() 也可以正常工作。此外,在最新版本的 Firefox 中各种方法之间没有可察觉的性能差异。当然,硬件/软件堆栈中可能正在进行大量缓存,但这实际上是重点-让机器完成工作。在廉价智能手机上可能会有所不同。干杯!


7
这个问题取决于你实际想要做什么。通常情况下,不要使用document.write,而是可以使用someElement.innerHTML或更好的方法是使用document.createElement和someElement.appendChild。你也可以考虑使用像jQuery这样的库,并使用其中的修改函数:http://api.jquery.com/category/manipulation/

6

4

尝试使用getElementById()或getElementsByName()来访问特定元素,然后使用innerHTML属性:

<html>
    <body>
        <div id="myDiv1"></div>
        <div id="myDiv2"></div>
    </body>

    <script type="text/javascript">
        var myDiv1 = document.getElementById("myDiv1");
        var myDiv2 = document.getElementById("myDiv2");

        myDiv1.innerHTML = "<b>Content of 1st DIV</b>";
        myDiv2.innerHTML = "<i>Content of second DIV element</i>";
    </script>
</html>

没能完全成功。脚本中不需要加<body>标签和</b>吗? - DarkLightA
@DarkLightA:你输入的默认源与上面的不同。另一方面,它在JavaScript窗口中!该源具有两种源类型,HTML和JavaScript。在您的桌面上创建一个带有该源的HTML文件并打开它。 - user432219

0
你可能会发现这个方法很有用。它是一个纯JS方法,可以接受字符串或节点作为输入。我写这个方法是为了支持Adobe Target跟踪。
function documentWrite(newNode) {
  // convert string to a fragment
  if (typeof newNode === 'string') {
    newNode = document.createRange().createContextualFragment(newNode);
  }
  var body = document.querySelector('body')
  if (body) {
    body.appendChild(newNode)
  }
}

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