JAVASCRIPT相容性問題

逸卿發表於2014-04-13
一、檢測瀏覽器的名稱
問題:
    
不同的瀏覽器對JavaScript的標準支援也有不同,有時希望指令碼能夠在不同的瀏覽器上都能執行良好,這時需要對瀏覽器進行檢測,確定其名稱,以針對不同的瀏覽器編寫相應的指令碼。
解決方案:
    
使用navigator物件的appName屬性。
    
比如,要檢測瀏覽器是否為IE,可以這麼做:
    var isIE = (navigator.appName == "Microsoft Internet Explorer");
    document.write("is IE?" + isIE);

    
對於FireFoxnavigator物件的appName屬性值為"Netscape"Opera9.02appName屬性值為"Opera"(其更早版本可能不同)

二、檢測瀏覽器的版本號:
問題:
    
隨著瀏覽器的版本的更迭,瀏覽器所支援的指令碼特性也在變化,有時候就需要針對不同的版本編寫相應的指令碼,那麼如何獲得瀏覽器的版本號?
解決方案:
    
通過解析navigator物件的userAgent屬性來獲得瀏覽器的完整版本號。
    IE
將自己標識為MSIE,後面帶一個空格,版本號以及分號。所以我們只要取空格和分號之間的部分即可。如Windows XP SP2所帶的IEuserAgent屬性值為"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)",可以看到其版本為6.0。可以用如下的函式來獲取IE瀏覽器的版本號:
    function getIEVersonNumber()
    {
        
var ua = navigator.userAgent;
        
var msieOffset = ua.indexOf("MSIE ");
        
if(msieOffset < 0)
        {
            
return 0;
        }
        
return parseFloat(ua.substring(msieOffset + 5, ua.indexOf(";", msieOffset)));
    }

假設我們要為IE5及以上版本編寫指令碼,可以這麼寫:
    var isIE5Min = (getIEVersonNumber() >= 5);
    
if(isIE5Min)
    {
        
// perform statements for IE 5 or later
    }

    
對於FireFoxOpera等瀏覽器,也可以用navigator.userAgent屬性來獲取其版本號,只不過其形式與IE有所不同,如FireFox
    Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.0.7) Gecko/20060909 Firefox/1.5.0.7
    Opera
Opera/9.02 (Windows NT 5.1; U; en)根據這些形式,我們不難獲得其版本號。但這些瀏覽器的其它版本沒有測試過,其具體值不明確,如果要使用這種方法檢測,請自行驗證。

    
下面討論下,上面的那段為IE5及以上版本瀏覽器編寫的指令碼,使用這種寫法要注意:要用>=而不是==,一般情況下,我們可以假定瀏覽器是向後相容的,所以使用==顯然不能適應新版本;另一方面,我們上面的假定也僅僅是假定,不能確保是這樣,如果瀏覽器的某些物件或屬性不能向後相容,我們的程式碼也會產生問題,所以建議,少用瀏覽器版本的比較,更多情況下,應檢測是要用的物件或屬性是否得到支援。

三、檢測客戶端的作業系統型別
根據上面的討論可以看到,navigator.userAgent屬性通常含有作業系統的基本資訊,但很不幸,沒有統一的規則去根據userAgent獲取準確的作業系統資訊,因為這些值與瀏覽器的種類、瀏覽器的版本甚至瀏覽器的OEM版本都有關係。
通常我們能做的是,檢測一些更為通用的資訊,比如作業系統是Windows還是Mac,而不是去看是Windows 98還是Windows XP。其規則是所有的Windows版本都會含有"Win",所有的Macintosh版本都含有"Mac",所有的Unix則含有"X11",而在Linux下則同時包含"X11""Linux"。如:
    var isWin = (navigator.userAgent.indexOf("Win") != -1);
    
var isMac = (navigator.userAgent.indexOf("Mac") != -1);
    
var isUnix = (navigator.userAgent.indexOf("X11") != -1);

通常用在為不同的作業系統設定不同的字型或位置等樣式。

四、檢測瀏覽器對特定物件的支援
問題:
    
如果需要編寫對多種瀏覽器或瀏覽器的多個版本都能適用的指令碼,就要進行檢測一下,瀏覽器是否支援某個物件。當然這種檢測主要是針對那些潛在的不相容物件的語句。
解決方案:
    
早期的瀏覽器對於img元素的支援差別很大,所以要在指令碼中操作img元素,需要檢測瀏覽器是否支援。這時我們不需要對所有可能的瀏覽器一一檢測,只需在必要的地方用下面的方式檢測:
    function rollover(imgName, imgSrc)
    {
        
// 如果支援images物件
        if(document.images)
        {
            
// statements go here
        }
    }

    
這種方法能夠生效是基於一個事實:如果document.images物件不存在,那麼if求值的結果為false

    
使用這種方法,使得對物件的檢測變得簡單易行,但是我們要注意,對於那些不支援該物件的瀏覽器要如何較好得處理。看下面的程式碼:
    function getImgAreas()
    {
        
var result = 0;
        
for(var i = 0; i < document.images.length; i++)
        {
            result += (document.images[i].width * document.images[i].height);
        }
        
return result;
    }
   
    
function reportImageArea()
    {
        document.form1.imgData.value = getImgAreas();
    }

    
這裡沒用物件支援的檢測。如果瀏覽器支援document.images,這兩個函式執行正常;否則就會丟擲異常。下面是改進的指令碼:
    function getImgAreas()
    {
        
var result;
        
// 檢測瀏覽器是否支援物件
        if (document.images)
        {
            result = 0;
            
for (var i = 0; i < document.images.length; i++)
            {
                result += (document.images[i].width * document.images[i].height);
            }
        }
        
// 返回值為一個數字或null
        return result;
    }
    
function reportImageArea()
    {
        
// 現在可以判斷返回值
        var imgArea = getImgAreas();
        
var output;
        
if (imgArea == null)
        {
            
// 對於不支援images物件的瀏覽器也要給出相應資訊
            output = "Unknown";
        } 
else {
            output = imgArea;
        }
        document.reportForm.imgData.value = output;
    }

這樣,不管瀏覽器是否支援該物件,都能給使用者比較合理的資訊,而不會跳出突兀的錯誤資訊。

五、檢測瀏覽器對特定屬性和方法的支援
問題:
    
檢測一個物件是否含有某個特定的屬性或方法。
解決方案:
    
大多數情況下,可以用類似於下面的程式碼來判斷:
    if(objectTest && objectPropertyTest)
    {
        
// OK to work with property
    }

    
先檢測物件是否存在,然後再檢測物件的屬性是否存在。如果物件確實不存在,該方法有效;如果屬性存在,但其值為null, 0, falseif語句求值的結果也將是false!所以這種方法並不安全,最好的方法是這樣:
    if (objectReference && typeof(objectReference.propertyName) != "undefined")
    {
        
// OK to work with property
    }

    
對於方法的檢測也可用類似的方法:
    function myFunction()
    {
        
if (document.getElementById)
        {
            
// 這裡可以使用getElementById方法
        }
    }
 
 
 
2----------------------
1.document.form.item 問題 
(1)
現有問題: 
現有程式碼中存在許多 document.formName.item("itemName") 這樣的語句,不能在 MF 下執行 
(2)
解決方法: 
改用 document.formName.elements["elementName"] 
(3)
其它 
參見 2 

2. 
集合類物件問題 
(1)
現有問題: 
現有程式碼中許多集合類物件取用時使用 ()IE 能接受,MF 不能。 
(2)
解決方法: 
改用 [] 作為下標運算。如:document.forms("formName") 改為 document.forms["formName"] 
又如:document.getElementsByName("inputName")(1) 改為document.getElementsByName("inputName")[1] 
(3)
其它 

3. window.event 
(1)
現有問題: 
使用 window.event 無法在 MF 上執行 
(2)
解決方法: 
MF 
 event 只能在事件發生的現場使用,此問題暫無法解決。可以這樣變通: 
原始碼(可在IE中執行) 
<input type="button" name="someButton" value="
提交" onclick="javascript:gotoSubmit()"/> 
... 
<script language="javascript"> 
function gotoSubmit() { 
... 
alert(window.event); // use window.event 
... 
} 
</script> 

新程式碼(可在IEMF中執行) 
<input type="button" name="someButton" value="
提交" onclick="javascript:gotoSubmit(event)"/> 
... 
<script language="javascript"> 
function gotoSubmit(evt) { 
evt = evt ? evt : (window.event ? window.event : null); 
... 
alert(evt); // use evt 
... 
} 
</script> 
此外,如果新程式碼中第一行不改,與老程式碼一樣的話( gotoSubmit 呼叫沒有給引數),則仍然只能在IE中執行,但不會出錯。所以,這種方案 tpl 部分仍與老程式碼相容。 

<script language="javascript"> 
function run(evnt) { 
if(!document.all) { 
alert(evnt.target.tagName); 
} 
else { 
alert("IE
下無法看到效果!"); 
} 
} 
</script> 

<body> 
<table width="200" border="1" onclick="run(event);"> 
<tr> 
<td bgcolor="#9900CC" onClick="run(event);">1</td> 
<td>2</td> 
<td>3</td> 
</tr> 
<tr> 
<td>11</td> 
<td>22</td> 
<td>33</td> 
</tr> 
<tr onmouseover="run();"> 
<td>111</td> 
<td>222</td> 
<td>333</td> 
</tr> 
</table> 
</body> 
4. HTML 
物件的 id 作為物件名的問題 
(1)
現有問題 
 IE 中,HTML 物件的 ID 可以作為 document 的下屬物件變數名直接使用。在 MF 中不能。 
(2)
解決方法 
 getElementById("idName") 代替 idName 作為物件變數使用。 

5. 
idName字串取得物件的問題 
(1)
現有問題 
IE中,利用 eval(idName) 可以取得 id  idName  HTML 物件,在MF 中不能。 
(2)
解決方法 
 getElementById(idName) 代替 eval(idName) 

6. 
變數名與某 HTML 物件 id 相同的問題 
(1)
現有問題 
 MF 中,因為物件 id 不作為 HTML 物件的名稱,所以可以使用與 HTML 物件 id 相同的變數名,IE中不能。 
(2)
解決方法 
在宣告變數時,一律加上 var ,以避免歧義,這樣在 IE 中亦可正常執行。 
此外,最好不要取與 HTML 物件 id 相同的變數名,以減少錯誤。 
(3)
其它 
參見問題4 

7. event.x 
 event.y 問題 
(1)
現有問題 
IE 中,event 物件有 x, y 屬性,MF中沒有。 
(2)
解決方法 
MF中,與event.x 等效的是 event.pageX。但event.pageX IE中沒有。 
故採用 event.clientX 代替 event.x。在IE 中也有這個變數。 
event.clientX 
 event.pageX 有微妙的差別(當整個頁面有滾動條的時候),不過大多數時候是等效的。 

如果要完全一樣,可以稍麻煩些: 
mX = event.x ? event.x : event.pageX; 
然後用 mX 代替 event.x 
(3)
其它 
event.layerX 
 IE  MF 中都有,具體意義有無差別尚未試驗。 


8. 
關於frame 
(1)
現有問題 
 IE可以用window.testFrame取得該framemf中不行 
(2)
解決方法 
frame的使用方面mfie的最主要的區別是: 
如果在frame標籤中書寫了以下屬性: 
<frame src="xx.htm" id="frameId" name="frameName" /> 
那麼ie可以通過id或者name訪問這個frame對應的window物件 
mf只可以通過name來訪問這個frame對應的window物件 
例如如果上述frame標籤寫在最上層的window裡面的htm裡面,那麼可以這樣訪問 
ie
 window.top.frameId或者window.top.frameName來訪問這個window物件 
mf
只能這樣window.top.frameName來訪問這個window物件 

另外,在mfie中都可以使用window.top.document.getElementById("frameId")來訪問frame標籤 
並且可以通過window.top.document.getElementById("testFrame").src = 'xx.htm'來切換frame的內容 
也都可以通過window.top.frameName.location = 'xx.htm'來切換frame的內容 
關於framewindow的描述可以參見bbs‘windowframe’文章 
以及/test/js/test_frame/目錄下面的測試 
----adun 2004.12.09
修改 

9. 
mf中,自己定義的屬性必須getAttribute()取得 
10.
mf中沒有 parentElement parement.children 而用 
parentNode parentNode.childNodes 
childNodes
的下標的含義在IEMF中不同,MF使用DOM規範,childNodes中會插入空白文字節點。 
一般可以通過node.getElementsByTagName()來回避這個問題。 
html中節點缺失時,IEMFparentNode的解釋不同,例如 
<form> 
<table> 
<input/> 
</table> 
</form> 
MF
input.parentNode的值為form, IEinput.parentNode的值為空節點 

MF
中節點沒有removeNode方法,必須使用如下方法 node.parentNode.removeChild(node) 

11.const 
問題 
(1)
現有問題: 
 IE 中不能使用 const 關鍵字。如 const constVar = 32; IE中這是語法錯誤。 
(2)
解決方法: 
不使用 const ,以 var 代替。 

12. body 
物件 
MF
bodybody標籤沒有被瀏覽器完全讀入之前就存在,而IE則必須在body完全被讀入之後才存在 

13. url encoding 
js中如果書寫url就直接寫&不要寫&例如var url = 'xx.jsp?objectName=xx&objectEvent=xxx'; 
frm.action = url
那麼很有可能url不會被正常顯示以至於引數沒有正確的傳到伺服器 
一般會伺服器報錯引數沒有找到 
當然如果是在tpl中例外,因為tpl中符合xml規範,要求&書寫為& 
一般MF無法識別js中的& 


14. nodeName 
 tagName 問題 
(1)
現有問題: 
MF中,所有節點均有 nodeName 值,但 textNode 沒有 tagName 值。在 IE 中,nodeName 的使用好象 
有問題(具體情況沒有測試,但我的IE已經死了好幾次)。 
(2)
解決方法: 
使用 tagName,但應檢測其是否為空。 

15. 
元素屬性 
IE
 input.type屬性為只讀,但是MF下可以修改 


16. document.getElementsByName() 
 document.all[name] 的問題 
(1)
現有問題: 
 IE 中,getElementsByName()document.all[name] 均不能用來取得 div 元素(是否還有其它不能取的元素還不知道)。
 
第一:onload

    
網頁載入完執行的函式,這個程式碼是從十大常用javascript的函式裡面摘取的,當然有其他的實現方法,但這個函式寫的真的非常巧妙。從效率方面也是一個非常值得使用的函式!
以下就是具體程式碼:
//--------------------------------------------------------
function addLoadEvent(func) {
var oldonload = window.onload;
if (typeof window.onload 
= 'function') {
window.onload = func;
}
else {
window.onload = function() {
oldonload();
func();
}
}
}
//--------------------------------------------------------
    
我不想一句一句的分析這個addLoadEvent,那是高手們很難接受的!我們就來說說它的相容性!請看函式中window.onload,作者為什麼不用document.body.onload呢?那是因為在Firefox[以下全部簡稱ff]document.body.onloadundefined(未定義),把一個函式賦值給undefined既不會發生什麼事情,也不算出錯!這個是讓人頭痛!好的,知道相容性的厲害了吧?那麼,以後在編寫程式碼時注意一下就好了! 
第二:body
    
這個body物件也是困擾我們的東西,叫它物件不知道對不對?我不是學計算機專業的,專業術語還真不大清楚!
以下就是具體程式碼:
//--------------------------------------------------------
function getPageScroll(){
var yScroll;
if (self.pageYOffset) {
yScroll = self.pageYOffset;
}
// Explorer 6 Strict
else if (document.documentElement && document.documentElement.scrollTop){
yScroll = document.documentElement.scrollTop;
} else if (document.body) {// all other Explorers
yScroll = document.body.scrollTop;
}
arrayPageScroll = new Array(''
yScroll)
return arrayPageScroll;
}
//--------------------------------------------------------
    
這個函式作用是:瀏覽器滾動條滾動的高度讀取。這是從lightbox中摘錄的,讀讀這段程式碼,看到三次判斷:
1.if (self.pageYOffset)
ff進行判斷處理;
2.if (document.documentElement && document.documentElement.scrollTop)
是對網頁標準(xHTML 1.1 DTD)的判斷,這裡要說明一下:在加入 xHTML 1.1 DTD 檔案頭時document.body.scrollTop之類的值往往是0,而這個是很難被除錯察覺的(我曾因此困惑很久,這裡吐血傳授經驗啦!)
3.
則是我們常用document.body.scrollTop
    
從這三次判斷,可以想象作者思維的嚴密了!當然這是程式設計師的共性!

第三:attachEvent/addEventListener
    
這裡是比較直接的區別,可是太多的直接卻造成了程式設計的困擾,這不禁使人想起:到底有多少這樣的直接不同函式?他們的差別到底在哪裡?畢竟大家的大腦空間有限,這麼多怎麼記?可是這些是必須記住的!沒事,這篇文章裡常見的js相容性都提到了,可以作為家居旅行,隨身攜帶的小手冊!
以下就是具體程式碼:
//--------------------------------------------------------
_observeAndCache
 function(element name observer useCapture) {
if (
this.observers) this.observers = [];
if (element.addEventListener) {
this.observers.push([element
 name observer useCapture]);
element.addEventListener(name
 observer useCapture);
} else if (element.attachEvent) {
this.observers.push([element
 name observer useCapture]);
element.attachEvent('on' + name
 observer);
}
}
//--------------------------------------------------------
    
意思是給物件新增事件。這是Sam Stephensonprototype中類的一部分,舉這段程式碼,不是讓你去慢慢分析那個prototype.js檔案,只是說明在ieOpera下就可以使用obj.attachEvent(),但在ff下卻只能使用obj.addEventListener()
類似區別的還有:
detachEvent/removeEventListener
parentElement/parentNode
insertAdjacentElement/appendChild
srcElement/target
onmousewheel/DOMMouseScroll
clientY/pageY

第四:物件引用
1.getElementById
請看以下程式碼:
<!-- 1 -->
<input id="t1"><input type="button"
value="click me" onclick="alert(t1.value)">
<!-- 2 -->
<input id="t1"><input type="button"
value="click me" onclick="alert(document.getElementById('t1').value)">
    
兩個都是獲取文字框的值,但後者的相容性就比前者好!對於IE來說,一個HTML 元素的ID可以直接在指令碼中當作變數名來使用,而ff中不可以。
getElementById
這個函式是非常有用、通用的函式,所以在引用物件時我們要儘量使用它!
2.var
請看以下程式碼:
//--------------------------------------------------------
echo=function(str){
document.write(str);
}
//--------------------------------------------------------
    
這個函式在ie上執行正常,ff下卻報錯了,而在echo前加上var就正常了,這個就是我們提到var的目的。
3.[]
    document.forms(”formName”) 
改為 document.forms[”formName”]目的:現有程式碼中許多集合類物件取用時使用 ()ie 能接受,ff 卻不能。
4.frame
的引用
    ie
可以通過id或者name訪問這個frame對應的window物件,而mf只可以通過name來訪問這個frame對應的window物件。

第五:指令碼執行
    
讓我們分別做個試驗,請出ieff分別執行一下下面一段js
//--------------------------------------------------------
o={
foo
 function(){
alert("fly");
}
};
with (o) {
bar();
function bar(){
alert("fly");
}
foo();
}
//--------------------------------------------------------
    IE
下,上面的程式碼成功輸出flyff報錯:bar未定義!
    
當然這是一個小小的試驗,大家很明顯的看出ieff的支援執行的情況:ie指令碼預解釋執行,ff指令碼順序執行!這是javascript編寫和設計時必須注意的東西!

第六:XMLHttpRequest物件
請看以下程式碼
//--------------------------------------------------------
function createRequest(){
if(typeof XMLHttpRequest
="undefined")? {
return new XMLHttpRequest();
}else if(typeof ActiveXObject
="undefined"){
var xmlHttp_ver? = false;
var xmlHttp_vers = [
"MSXML2.XmlHttp.5.0"

"MSXML2.XmlHttp.4.0"

"MSXML2.XmlHttp.3.0"

"MSXML2.XmlHttp"

"Microsoft.XmlHttp"
];
if(
xmlHttp_ver){
for(var i=0;i<xmlHttp_vers.length;i++){
try{
new ActiveXObject(xmlHttp_vers[i]);
xmlHttp_ver = xmlHttp_vers[i];
break;
}catch(oError){;}
}
}
if(xmlHttp_ver){
return new ActiveXObject(xmlHttp_ver);
}else{
throw new Error("Could not create XML HTTP Request.");
}
}else{
throw new Error("Your browser doesn't support an XML HTTP Request.");
}
}
//--------------------------------------------------------
    
意思是:得到XMLHttpRequest物件,是喜悅村裡的一個兄弟寫的。在ie下,一句new ActiveXObject("MSXML2.XMLHTTP")就可以搞定的東西,但這裡我們花了這麼多行程式碼來解決相容性問題,這個函式作者更從原理入手:xmlHttp_vers 應該從版本高的往版本低的寫,這樣建立物件的資料呼叫的是你機子上安裝過的最高版本的MSXML2.XmlHttp。十分巧妙和有效地得到了物件!

    
好了,其實關於javascript相容性的例子還有很多,我們無法羅列所有,這裡做了個簡單介紹,更多的只能在程式設計工程中去慢慢體會了!當然本文僅僅討論了js的相容性,同時css的相容性問題也是不可忽視的!解決相容性最好的方法就是封裝!
 
3----瀏覽器相容性
JavaScript程式設計的最大問題來自不同的瀏覽器對各種技術和標準的支援。構建一個執行在不同瀏覽器(如IE和火狐)是一個困難的任務。因此幾種AJAX JavaScript框架或者生成基於服務端邏輯或標記庫的JavaScript,或者提供符合跨瀏覽器AJAX開發的客戶端JavaScript庫。一些流行的框架包括:AJAX.Net, Backbase, Bitkraft, Django, DOJO, DWR, MochiKit, Prototype, Rico, Sajax, Sarissa, and Script.aculo.us. 

  這些框架給開發人員更多的空間使得他們不需要擔心跨瀏覽器的問題。雖然這些框架提升了開發人員構建應用的能力,但由於廠商已經開發了更細節的使用者介面的打包元件解決方案,因此在AJAX元件市場中需要考慮一些其他因素。例如提供通用使用者介面的元件如組合框和資料柵格的幾個廠商,都可以被用來在應用中建立良好的通過類似電子資料表方式來檢視和編輯資料的體驗。但這些元件不僅是封裝了元件的使用者介面而且包括與服務端資料的通訊方式,這些元件通常使用基於標記方式來實現如ASP.Net或JSF控制元件。
Ajax在本質上是一個瀏覽器端的技術,首先面臨無可避免的第一個問題即是瀏覽器的相容性問題。各家瀏覽器對於JavaScript/DOM/CSS的支援總有部分不太相同或是有Bug,甚至同一瀏覽器的各個版本間對於JavaScript/DOM/CSS的支援也有可能部分不一樣。這導致程式設計師在寫Ajax應用時花大部分的時間在除錯瀏覽器的相容性而非在應用程式本身。因此,目前大部分的Ajax連結庫或開發框架大多以js連結庫的形式存在,以定義更高階的JavaScript APIJavaScript物件(模板)、或者JavaScript Widgets來解決此問題。如prototype.js
 

相關文章