页面打开一片空白,右键看源码明明有内容,渲染出来什么都没有。最后定位到一段广告脚本,里面调了 document.write,而它的执行时机在页面加载完成之后。

document.write 的行为全看时机。在解析 HTML 的过程中调用,内容会被插到当前脚本标签的位置,正常显示。页面 load 完成之后再调用,浏览器会先清空整个文档,再写入新内容,原来的东西全没了。看起来像白屏,其实是整个文档被换掉了。

什么时候一定会踩坑

  • defer 和 async:这两种脚本在文档解析后执行,里面调用 write 会覆盖页面
  • 事件回调里:点击、定时器里调用,页面早已加载完,必覆盖
  • iframe:同源 iframe 里调用会写入该 iframe 的文档,跨域直接报错
  • XHTML:不支持 write,调用没有效果,也不报错

还有个子坑,write 连续调用是有状态的。解析期连续写多段内容没问题,加载完调一次就开一个新文档,再调第二次仍然是覆盖,不会追加成两段。

老代码为什么还在用

统计脚本、广告位、早年间的三方 SDK 常用它,因为那个年代这是最容易在任意位置插入内容的方式,不用等 DOM 就绪,也不用管容器节点存不存在。代价是会阻塞解析,脚本执行完页面才继续渲染,慢的时候能明显看到白屏时间变长。

var el = document.createElement(TAG); el.innerHTML = html; document.getElementById(SLOT).appendChild(el);

改成这种写法,插入时机由你控制,也不会清空文档。要动态加载脚本,用 createElement 建 script,设好 src 和 async,再挂到 head 上,比 write 一行字符串清楚得多。

维护老系统时会遇到只能靠 write 输出的三方代码,改不动。那就把脚本放在 body 里靠前的位置,别加 async,别放 defer,让它保持解析期执行。这是折中,不是最佳实践,能换掉的尽早换。