初春的晚上,閑來無事,聊聊 document.write 方法。
document.write 使用方式非常簡單,把 "字符串化"(不好意思,這可能是我自己創造的名詞)的 html 代碼當做參數傳入就 ok 了,我並不打算講它的基本用法,可以參考以下鏈接:
document.write 經常會被用來加載腳本,比如這樣:
var url = 'http://ads.com/buyme?rand='+Math.random()
document.write('<script src="'+url+'"></scr'+'ipt>')
傳統方式:
var script = document.createElement('script')
script.src = 'http://ads.com/buyme?rand='+Math.random()
// now append the script into HEAD, it will fetched and executed
document.documentElement.firstChild.appendChild(script)
對比 dom 插入的傳統方法,的確能少幾行代碼。這樣做還有個好處,它比 dom 插入的方式快,因為它是在一個輸出流中,所以不用修改 dom 結構(It is very fast, because the browser doesn’t have to modify an existing DOM structure)。但是,如果這段腳本沒有執行完,后續渲染都將掛起!
document.write 加載腳本也不是沒有合適的場景,比如說后續的渲染都要依賴這段腳本,那么這樣寫就完全沒有問題。比如這段代碼:
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.6.3/jquery.min.js"></script>
<script>window.jQuery || document.write('<script src="js/libs/jquery-1.6.3.min.js"><\/script>')</script>
或者:
<script>window.JSON || document.write('<script src="json2.js"><\/script>')</script>
非常的優雅。
還有個應用場景,加載第三方廣告,百度聯盟的廣告就是用該方法輸出的。我們假設百度聯盟廣告如下(另存為 cm.js):
document.write("<img src='ad.jpg /'>");
那么我們在頁面任意部分同步加載這段代碼,就能顯現百度廣告,事實上,體驗是非常差的,因為是同步渲染,如果這段代碼沒有執行完,后續是不會執行下去的(UI 掛起)。嘗試着將內含 document.write 的腳本文件異步執行,寫個簡單的 demo。
index.htm 文件:
<body>
Hello
<script>
var s = document.createElement("script");
s.src = "data.js";
document.body.appendChild(s);
</script>
</body>
data.js 文件:
document.write('World');
頁面只顯示了 Hello 字樣,控制台打印 notice 如下(詳見 stackoverflow):
按照 notice 的提示將 document.open() 加入 data.js 文件,這時頁面就只有 World 了。我去,異步加載個 js,替換這個頁面,這樣的操作應該幾乎沒有吧!所以,看起來百度的廣告只能同步加載了,如果延遲加載(用個 setTimeout 方法)用到 document.write 的文件,那么理論上會覆蓋整個頁面吧,這是我們不希望看到的,也是我們要謹慎使用該方法的原因( Why is document.write considered a “bad practice”?)。
用 document.write 加載腳本文件,甚至還涉及到瀏覽器的兼容性,不同的瀏覽器會用不同的順序加載,這點不展開了,有興趣的可以參考如下鏈接:
最后總結下吧,如果用 document.write 來渲染頁面,可以適當適時的使用,如果是加載腳本,盡量別用了,畢竟 stevesouders 建議別用(Don’t docwrite scripts),主要還是為了不影響后續的加載。
附以前寫的草稿:
document.write 是 document 下的一個方法,很多入門書籍中經常見到該方法,實際生產中卻很少用到。
document.write() 接收一個字符串作為參數,將該字符串寫入文檔流中。一旦文檔流已經關閉(document.close()),那么 document.write 就會重新利用 document.open() 打開新的文檔流並寫入,此時原來的文檔流會被清空,已渲染好的頁面就會被清除,瀏覽器將重新構建 DOM 並渲染新的頁面。
向文檔流中寫入 HTML 字符串:
<div>
<script>
document.write("<script src='cm.js'><\/script>");
document.write("<div class='add'></div>")
</script>
</div>
因為 document.write 方法作用時,文檔流還沒關閉,所以並不用先 document.open()。渲染完后頁面 dom 結構( chrome下 需考慮瀏覽器兼容性):
<div>
<script>
document.write("<script src='cm.js'><\/script>");
document.write("<div class='add'></div>")
</script>
<script src="cm.js"></script>
<div class="add"></div>
</div>
這里還需要 注意一點,當 document.write 的字符串參數包含 script 標簽時,注意要轉義,或者將 </script>
割開(split),比如 document.write("<script src='cm.js'></" + "script>");
,這是因為一旦遇到 </script>
,會自動與包裹該段代碼的 <script>
進行配對。詳見 這里。
再看個例子:
<div>
<p>hello world</p>
</div>
<script>
setTimeout(function() {
document.write('<a href="http://www.cnblogs.com/zichi/">zichi\'s blog</a>');
}, 0);
</script>
因為當 setTimeout 的回調執行時,文檔流已經關閉(頁面已經解析完),所以首先自動調用 document.open() 函數打開文檔流,然后將文檔流清空,渲染新的東西,即字符串中的 a 標簽。
既然不加 document.open() 也會自動開啟文檔流,那么 document.open() 以及 document.close() 是否沒用武之地了呢?思考如下代碼。
代碼一:
<div>
<p>hello world</p>
</div>
<script>
setTimeout(function() {
document.write('a');
document.write('b');
}, 0);
</script>
代碼二:
<div>
<p>hello world</p>
</div>
<script>
setTimeout(function() {
document.open();
document.write('a');
document.close();
document.open();
document.write('b');
document.close();
}, 0);
</script>
前者頁面顯示 "ab",而后者顯示 "b"。可以想象前者兩個 document.write 在一個文檔流中輸出,而后者手動關閉文檔流,所以相當於重寫了兩次。
繼續看:
<div>
<p>hello world</p>
</div>
<script>
document.open();
document.write('a');
document.close();
document.open();
document.write('b');
document.close();
</script>
頁面上 "hello world" 和 "ab" 都在。可以想象,當頁面初次載入,瀏覽器還沒解析完時,就算手動關閉文檔流,也是關不掉的。