document.write
是JavaScript中對document.open
所開啓的文檔流(document stream操作的API方法,它能夠直接在文檔流中寫入字符串,一旦文檔流已經關閉,那document.write
就會重新利用document.open
打開新的文檔流並寫入,此時原來的文檔流會被清空,已渲染好的頁面就會被清除,瀏覽器將重新構建DOM並渲染新的頁面。
--(重寫頁面問題!!頁面已經加載完,用它就清空以前的document)
一.寫入文本(頁面加載中可以寫入,而不會出現重寫頁面問題)
下面來看看如何在利用document.write
來寫入腳本。先考慮如下代碼:
<!DOCTYPE HTML>
<html>
<head>
</head>
<body>
<h1>Head</h1>
<script>
document.write('<p>hello document</p>');
</script>
<h2>Tail</h2>
</body>
</html>
這段在h1
和h2
之間內嵌一個腳本,使用document.write
來寫入一個p
標籤。
刷新頁面,可以看到最終的結果是
Head
hello document
Tail
即要文本在腳本執行的位置被插入。這是因爲,瀏覽器就解析HTML構建DOM的時候,如果遇到script
就會暫停下來,解析script
中的代碼並執行,然後再繼續解析剩餘HTML。(阻塞進行的)
此時再去瀏覽器中檢測DOM的結構,會發現script
與h2
之間多了一個p
,瀏覽器在解析完h1
之後,碰到script
並執行之,此時document.write
將一段HTML代碼寫入到文檔流中,script執行完畢後,瀏覽器會解析文檔流中的字符串,對新添加的p
標籤進行解析。
如果將渲染好的頁面保存下來,不同的瀏覽器會有不同的結果。如Chrome和Firefox的做保存下來的頁面文件中,script
後面會增加p
標籤,而IE中則是維持原狀。(這裏指的是原有的HTML結構,不同瀏覽器將頁面保存會作不同的處理,有些會增加一些不影響原有結構的標籤或註釋。這意味着,如果瀏覽器重新加載Chrome或Firefox中保存下來的頁面文件,就會多出一個p
標籤。(可以自己動手試試!畢竟實踐出真知)
二.寫入腳本(注意加轉義符號)
既然document.write
可以寫入p
並被瀏覽器解析,那麼自然地也可以寫入script
標籤。
<script>
document.write('<script>alert("oops!!!")</script>');
</script>
我們將代碼作出上面的改動,意圖在利用document.write
在頁面中插入一段腳本。這段代碼的本意是彈出一個窗口,阻塞瀏覽器對HTML的解析。
瀏覽器下刷新頁面,發現並不管用,取而代之的是顯示出一個沒有意料到的頁面。
Head
');
Tail
去檢查DOM樹,就會發現,這段腳本被攔腰截斷了!瀏覽器將它解析成以下代碼:
<script>
document.write('<script>alert("oops!!!")
</script>
');
插入文本中的</script>
被當成了第一個script
的閉合標籤,因此這個段代碼成了非法代碼,因爲document.write
的調用書寫不正確,缺少右邊的括號)
。此時,你可以在console中看到相關的錯誤信息。(控制檯)
爲了解決這個問題,我們可以對插入文本中閉合的的標籤進行輕微修改,對最後一個>
進行轉義,變成\>
。
此時再刷新一下頁面,就可以看到預想中的結果。即頁面中僅顯示h1
,彈窗阻塞了瀏覽器對HTML的解析,關閉彈窗後,瀏覽器繼續對HTML的解析並完成對頁面的渲染。
再去看看DOM的結構,會發現在原有的script
元素後面又多了一個新的<script>
元素,其中所執行的代碼就是我們的alert("opps!!!")
。
三.document.write使用的時機很重要(這麼叼,卻不喜歡用它!)
這樣看來,利用document.write
來在HTML中插入標籤非常方便,就如同讓瀏覽器在解析HTML的時候動態得添加標籤,而且只需要一行代碼即可,不需要使用document.createElement
再將其插入到DOM中。
但爲什麼大家都不建議使用document.write呢?這跟document.write
的實現機制有關。在討論之前,先看看下面的代碼:
<script>
setTimeout(function(){
document.write('<p>5 secs later</p>');
}, 5000);
</script>
對之前的代碼作簡單修改,這段代碼同樣是想插入一個p
元素,但它是在5秒以後才執行。
刷新頁面後,我們看到了這樣的顯示效果:
Head
Tail
但是5秒以後,卻變成了這樣:(頁面被重寫了!!)
5 secs later
原來的h1
、h2
甚至是script
,DOCTYPE
還有head
(當然,之前並沒有往head添加任何標籤,但如果添加了,這些標籤也會有同樣的下場),它們全部都不見了,取而代之的是一個基本的html結構,它是這樣的:
<html>
<head></head>
<body>
<p>5 secs later</p>
</body>
</html>
這是一個全新的頁面,document.write
將之前的頁面全部清除了,重新打開一頁面並在這個頁面上寫入了新的標籤。爲什麼會這樣呢?(5s後頁面加載完了,所以重寫頁面了!)
回到再文章開頭所描述文字,就會找到結果。這是因爲,5秒以後,瀏覽器早已完成了HTML的解析,並將文檔流給關閉了。5秒後,timeout
事件觸發,document.write
在執行的時候發現文檔流已經關閉了,就會重新調用document.open
打開一個新的文檔流,而document.open
的調用則會清除已有的文檔。所以,最終看到的顯示結果就是向上面那樣,之前存在的頁面都被清除掉了。
如果我們把document.write
調用放到DOMCOntentLoaded
或load
的事件處理中,也會得到同樣的結果。
這樣看到,除非是在瀏覽器關閉文檔之前調用document.write
,否則當前頁面都會被清除。
(說通俗點,如果能保證這貨在onload前執行,那麼可以實現載入,而不是重寫)
這一個特性決定了document.write
在實際開發中的應用範圍和時機。那麼,什麼時候應該使用document.write
呢?
在網上搜集的資料看,一般在下列情景下可以利用document.write
來完成某些特殊的操作:
加載需要配合JS腳本使用的外部CSS文件
利用下面的語句加載外部樣式文件:
<scirpt>
document.write('<link rel="stylesheet" href="style_neads_js.css">');
</script>
將所有需要用到JS的樣式都放到這個外部樣式表中,如果瀏覽器不禁用JS,那麼該樣式表就會被順利加載,否則頁面就不會使用該樣式。(Don’t docwrite scripts)
在新的窗口中寫入新的頁面數據時(新建一個頁面就不會重寫之前的)
既然在一個已加載完成的頁面中調用document.write
會重寫整個頁面,那麼在一個新的窗口的空白頁面中調用這個方法,就不存在這樣的的問題了。
另外,在調用document.write
,最好不要把document.open
和document.close
漏掉,儘管多數時候瀏覽器會幫忙完成這些操作。即,一個標準的document.write應該是這樣的:
document.open();
document.write('anthing')
document.close();
弊端
從某個角度說,document.write
的實際功能確實很強,能夠直接修改文檔流,但它有很多弊端:
- 在非loading階段調用
document.write
會清除已加載的頁面; - document.write不能夠在XHTML中使用;
- 嵌入
script
中的document.write
不能給任意節點添加子節點,因爲它是隨着DOM的構建執行的; - 利用
document.write
寫入HTML字符串流並不是一個好方法,它有違DOM操作的概念; - 利用
document.write
添加script
加載外部腳本時,瀏覽器的HTML解析會被script
的加載所阻塞;
總結
綜合上面所描述的關於document.write
的種種特點,個人感覺還是不到迫不得已的時候,不要去使用document.write,使用不當document.write不僅會影響頁面的性能,還容易造成各種bug。
要對DOM進行操作時,還是應當使用安全且對DOM的友好的API方法,以避免不必要的問題出現。
上述信息都是以自己做的小測試和網上的參考資料爲基礎總結出來,有錯誤的地方,歡迎大家指出,我會盡快作出修正。