innerhtml.htm.rar
在做 ajax 編程時,我們常常需要將 xmlhttp 擷取到的頁面內容通過 innerHTML 來賦給某個容器(比如 div、span 或者 td 等),但是這裡存在一個問題,就是我們將要賦給 innerHTML 的頁面內容如果包含有指令碼程式,這些指令碼程式不管是外部指令碼,還是內部指令碼,可能(1)都不會被執行。這個問題在某些時候微不足道,甚至可以忽略,但有些時候,這個問題就非常嚴重,它很可能讓我們的程式得不到預期的結果。因此我們需要解決這個問題。
如果你讀過 MSDN,你會發現並非所有插入到 innerHTML 中的指令碼都不能執行,如果這段指令碼的 script 標籤中包含了 defer 屬性,IE 會正確的執行這些指令碼程式。但不幸的是,Moziila/Firefox 和 Opera 可不吃這一套,不管 script 標籤有沒有設定 defer 屬性,這些瀏覽器都不會向 IE 那樣去執行插入到 innerHTML 中的指令碼。
但不管指令碼是否被執行了,有一點我們可以肯定,那就是這些指令碼確實被插入到了 innerHTML 中,如果不相信,你可以 alert 一下看看。但如果你真的 alert 了,你也可能會發現有一種例外情況存在,那就是如果指令碼在 innerHTML 內容開頭的話,那麼 IE 瀏覽器將會忽略掉這段指令碼,而 Moziila/Firefox 和 Opera 卻不會。
好了,問題分析的差不多了,我們來看看如何解決吧。
解決的思路其實很簡單,那就是將插入到 innerHTML 中的所有指令碼取出來,然後一一執行。不過我們先要解決上面的兩個問題。
先來看第一個問題,如何避免在 IE 中重複執行 innerHTML 中帶有 defer 屬性的指令碼。這個很容易,只需要先確定瀏覽器是否是 IE,然後再檢測將要執行的指令碼是否帶有 defer 屬性即可。需要注意的是,在判斷 IE 瀏覽器時,我們需要避免被 opera 的瀏覽器識別欺騙。這一點我們在後面的代碼中將會看到它是如何做的。
接下來,看 IE 忽略 innerHTML 開頭指令碼的問題,這個也很容易解決。只需要在要插入到 innerHTML 中的內容的開頭附加一段不是指令碼的內容,就可以了。但不要試圖附加一個空內容的標籤,或者空格、斷行符號、換行等,這將不起作用,開頭的指令碼仍然會被忽略。也不要試圖附加 ,雖然這可以讓開頭的指令碼不再被忽略,但這個 仍然會影響原有內容的顯示,雖然你可能覺得不明顯,但是對於挑剔的使用者來說,這可能是無法容忍的。因此,為了讓附加的內容既可以起到避免開頭指令碼被忽略的功能,又不會造成不良影響,我們將附加這麼一段內容:
<span style="display: none">hack ie</span>
雖然上面這段內容有一定的長度,但是它並不會顯示,而且這個插入的標籤沒有 id 也沒有 name,所以也不會跟原來內容中的某些標籤的 id 或者 name 產生衝突。不過這裡有一點要注意,這裡也要判斷是否是 IE,然後再決定加不加這段內容,因為其他某些瀏覽器可能不支援 display: none 這個 CSS 修飾(例如 Opera Mini),如果加上這段代碼會影響最終的顯示效果。
下面我們來看看如何取出指令碼並執行。
取出指令碼很容易,只需要用 innerHTML 所在對象的 getElementsByTagName 方法就可以了,這個方法對幾乎所有的容器標籤都管用。取出指令碼以後,我們要一一判斷它們是外部指令碼還是內部指令碼。
先來看外部指令碼,如果是外部指令碼,我們選擇了這樣一種方法,即先建立這個外部指令碼的一個副本對象,並設定它的 defer 屬性為 true(這一點是為了讓 IE 瀏覽器能正確執行),然後用 appendChild 方法將這個副本對象插入到 head 中。這裡你可能會問,為什麼不是插入到 innerHTML 所在的對象中呢?插入到 innerHTML 所在的對象中不是更好嗎?如果你試一下就會知道,如果插入到 innerHTML 所在的對象中,在 IE 瀏覽器中沒有問題,但是在 Mozilla/Firefox 和 Opera 瀏覽器中會有一些問題。問題是如果在 Firefox 上這樣做,瀏覽器會停止回應(這是在 Firefox 1.5 上的測試結果,其他版本是否有此問題,尚不得知),而在 Opera 上,指令碼會莫名其妙的執行兩次(這是在 Opera 8.5 上的測試結果,其它版本的 Opera 是否由此問題,也尚不得知)。為了避免這些問題,所以我選擇了插入到 head 中。
再來看內部指令碼,內部指令碼的內容我們可以直接用指令碼對象的 text 屬性來擷取,這裡我們使用指令碼對象的 text 屬性而不是 innerHTML 屬性,是因為在 Opera 瀏覽器中,指令碼對象的 innerHTML 屬性是空的,只有用 text 屬性才能擷取到指令碼內容。執行內部指令碼直接用 eval 即可。但是指令碼可能會被包含在 HTML 的注釋標籤中,因此我們需要先將注釋標籤去掉,不然在 IE 中會出錯。
——————————–
(1) 註:在這裡,我們用了限定詞“可能”,因為有一種情況,指令碼會被執行,在下文中你將會看到這種情況。
好了,有了上面的分析,我們就可以很容易的寫出一個讓插入到 innerHTML 中的 script 跑起來的程式了,下面就是這個程式的代碼:
下載: innerhtml.js
- /* innerhtml.js
- * Copyright Ma Bingyao <andot@ujn.edu.cn>
- * Version: 1.1
- * LastModified: 2006-01-31
- * This library is free. You can redistribute it and/or modify it.
- * http://www.coolcode.cn/?p=117
- */
-
- function set_innerHTML(obj, html) {
- var ie = navigator.appVersion.match(/MSIE/);
- var opera = (navigator.userAgent.toLowerCase().indexOf("opera") != -1);
- if (ie && !opera) {
- html = '<span style="display: none">hack ie</span>' + html;
- }
- obj.innerHTML = html;
- var scripts = obj.getElementsByTagName("script");
- if (scripts) {
- var script;
- for (var i = 0; i < scripts.length; i++) {
- script = scripts[i].text.replace(/(^\s*)|(\s*$)/g, "");
- if (!ie || opera || !scripts[i].defer) {
- if (scripts[i].src) {
- script = document.createElement("script");
- script.src = scripts[i].src;
- script.defer = true;
- script.type = scripts[i].type;
- var head = document.getElementsByTagName("head").item(0);
- head.appendChild(script);
- }
- else if (script.substr(0, 4) == "<!--") {
- eval(script.substr(4));
- }
- else {
- eval(script);
- }
- }
- }
- }
- }
示範程式地址:Demo
當然還有一點需要注意,如果這些指令碼中包含有 document.write 或者 document.writeln 之類的輸出語句,不要期望它們能夠在正確的位置被執行。至少上面的這個函數還不行,如果你有好的思路或辦法來解決這個問題,歡迎您提出來!