問題:
不同的瀏覽器對JavaScript的標準支持也有不同,有時希望腳本能夠在不同的瀏覽器上都能運行良好,這時需要對瀏覽器進行檢測,確定其名稱,以針對不同的瀏覽器編寫相應的腳本。
解決方案:
使用navigator對象的appName屬性。
比如,要檢測瀏覽器是否爲IE,可以這麼做:
document.write("is IE?" + isIE);
對於FireFox,navigator對象的appName屬性值爲"Netscape";Opera9.02的appName屬性值爲"Opera"(其更早版本可能不同);
二、檢測瀏覽器的版本號:
問題:
隨着瀏覽器的版本的更迭,瀏覽器所支持的腳本特性也在變化,有時候就需要針對不同的版本編寫相應的腳本,那麼如何獲得瀏覽器的版本號?
解決方案:
通過解析navigator對象的userAgent屬性來獲得瀏覽器的完整版本號。
IE將自己標識爲MSIE,後面帶一個空格,版本號以及分號。所以我們只要取空格和分號之間的部分即可。如Windows XP SP2所帶的IE的userAgent屬性值爲"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瀏覽器的版本號:
{
var ua = navigator.userAgent;
var msieOffset = ua.indexOf("MSIE ");
if(msieOffset < 0)
{
return 0;
}
return parseFloat(ua.substring(msieOffset + 5, ua.indexOf(";", msieOffset)));
}
假設我們要爲IE5及以上版本編寫腳本,可以這麼寫:
if(isIE5Min)
{
// perform statements for IE 5 or later
}
對於FireFox和Opera等瀏覽器,也可以用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 isMac = (navigator.userAgent.indexOf("Mac") != -1);
var isUnix = (navigator.userAgent.indexOf("X11") != -1);
通常用在爲不同的操作系統設置不同的字體或位置等樣式。
四、檢測瀏覽器對特定對象的支持
問題:
如果需要編寫對多種瀏覽器或瀏覽器的多個版本都能適用的腳本,就要進行檢測一下,瀏覽器是否支持某個對象。當然這種檢測主要是針對那些潛在的不兼容對象的語句。
解決方案:
早期的瀏覽器對於img元素的支持差別很大,所以要在腳本中操作img元素,需要檢測瀏覽器是否支持。這時我們不需要對所有可能的瀏覽器一一檢測,只需在必要的地方用下面的方式檢測:
{
// 如果支持images對象
if(document.images)
{
// statements go here
}
}
這種方法能夠生效是基於一個事實:如果document.images對象不存在,那麼if求值的結果爲false。
使用這種方法,使得對對象的檢測變得簡單易行,但是我們要注意,對於那些不支持該對象的瀏覽器要如何較好得處理。看下面的代碼:
{
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,這兩個函數運行正常;否則就會拋出異常。下面是改進的腳本:
{
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;
}
這樣,不管瀏覽器是否支持該對象,都能給用戶比較合理的信息,而不會跳出突兀的錯誤信息。
五、檢測瀏覽器對特定屬性和方法的支持
問題:
檢測一個對象是否含有某個特定的屬性或方法。
解決方案:
大多數情況下,可以用類似於下面的代碼來判斷:
{
// OK to work with property
}
先檢測對象是否存在,然後再檢測對象的屬性是否存在。如果對象確實不存在,該方法有效;如果屬性存在,但其值爲null, 0, false,if語句求值的結果也將是false!所以這種方法並不安全,最好的方法是這樣:
{
// OK to work with property
}
對於方法的檢測也可用類似的方法:
{
if (document.getElementById)
{
// 這裏可以使用getElementById方法
}
}
(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="提交" οnclick="javascript:gotoSubmit()"/>
...
<script language="javascript">
function gotoSubmit() {
...
alert(window.event); // use window.event
...
}
</script>
新代碼(可在IE和MF中運行):
<input type="button" name="someButton" value="提交" οnclick="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" οnclick="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 οnmοuseοver="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取得該frame,mf中不行
(2)解決方法
在frame的使用方面mf和ie的最主要的區別是:
如果在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對象
另外,在mf和ie中都可以使用window.top.document.getElementById("frameId")來訪問frame標籤
並且可以通過window.top.document.getElementById("testFrame").src = 'xx.htm'來切換frame的內容
也都可以通過window.top.frameName.location = 'xx.htm'來切換frame的內容
關於frame和window的描述可以參見bbs的‘window與frame’文章
以及/test/js/test_frame/目錄下面的測試
----adun 2004.12.09修改
9. 在mf中,自己定義的屬性必須getAttribute()取得
10.在mf中沒有 parentElement parement.children 而用
parentNode parentNode.childNodes
childNodes的下標的含義在IE和MF中不同,MF使用DOM規範,childNodes中會插入空白文本節點。
一般可以通過node.getElementsByTagName()來回避這個問題。
當html中節點缺失時,IE和MF對parentNode的解釋不同,例如
<form>
<table>
<input/>
</table>
</form>
MF中input.parentNode的值爲form, 而IE中input.parentNode的值爲空節點
MF中節點沒有removeNode方法,必須使用如下方法 node.parentNode.removeChild(node)
11.const 問題
(1)現有問題:
在 IE 中不能使用 const 關鍵字。如 const constVar = 32; 在IE中這是語法錯誤。
(2)解決方法:
不使用 const ,以 var 代替。
12. body 對象
MF的body在body標籤沒有被瀏覽器完全讀入之前就存在,而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 元素(是否還有其它不能取的元素還不知道)。
網頁加載完執行的函數,這個代碼是從十大常用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.onload是undefined(未定義),把一個函數賦值給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 Stephenson的prototype中類的一部分,舉這段代碼,不是讓你去慢慢分析那個prototype.js文件,只是說明在ie和Opera下就可以使用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" οnclick="alert(t1.value)">
<!-- 2 -->
<input id="t1"><input type="button"
value="click me" οnclick="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對象。
第五:腳本執行
讓我們分別做個試驗,請出ie和ff分別運行一下下面一段js:
//--------------------------------------------------------
o={
foo: function(){
alert("fly");
}
};
with (o) {
bar();
function bar(){
alert("fly");
}
foo();
}
//--------------------------------------------------------
IE下,上面的代碼成功輸出fly,ff報錯:bar未定義!
當然這是一個小小的試驗,大家很明顯的看出ie和ff的支持執行的情況: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的兼容性問題也是不可忽視的!解決兼容性最好的方法就是封裝!
這些框架給開發人員更多的空間使得他們不需要擔心跨瀏覽器的問題。雖然這些框架提升了開發人員構建應用的能力,但由於廠商已經開發了更細節的用戶界面的打包組件解決方案,因此在AJAX組件市場中需要考慮一些其他因素。例如提供通用用戶界面的組件如組合框和數據柵格的幾個廠商,都可以被用來在應用中創建良好的通過類似電子數據表方式來查看和編輯數據的體驗。但這些組件不僅是封裝了組件的用戶界面而且包括與服務端數據的通訊方式,這些組件通常使用基於標記方式來實現如ASP.Net或JSF控件。