javascript快速入門21--DOM總結

水之原發表於2013-12-01

跨瀏覽器開發

市場上的瀏覽器種類多的不計其數,它們的解釋引擎各不相同,期待所有瀏覽器都一致的支援JavaScript,CSS,DOM,那要等到不知什麼時候,然而開發者不能幹等著那天。歷史上已經有不少方法來解決瀏覽器相容問題了,主要分為兩種:1.userAgent字串檢測,2.物件檢測;當然,也不能考慮所有的瀏覽器,我們需要按照客戶需求來,如果可以確信瀏覽網站的使用者都使用或大部分使用IE瀏覽器,那麼你大可放心的使用IE專有的那些豐富的擴充套件,當然,一旦使用者開始轉向另一個瀏覽,那麼痛苦的日子便開始了。下面是市場上的主流瀏覽器列表:

  • Internet Explorer
  • Mozilla Firefox
  • Google Chrome
  • Opera
  • Safari

注意,瀏覽器總是不斷更新,我們不但要為多種瀏覽器作相容處理,還要對同一瀏覽器多個版本作相容處理。比如IE瀏覽器,其6.0版本和7.0版本都很流行,因為微軟IE隨著作業系統繫結安裝(之前也是同步發行,微軟平均每兩年推出一款個人桌面,同樣IE也每兩年更新一次;直到現在,由於火狐的流行,IE工作組才加快IE的更新),所以更新的較慢,6.0版和7.0版有很大差別。

市場上還存在一些其它瀏覽器,但由於它們都是使用的上面所列瀏覽器的核心,或與上面瀏覽器使用了相同的解釋引擎,所以無需多作考慮。下面是主流的瀏覽器解釋引擎列表:

  1. Trident

    Trident (又稱為MSHTML),是微軟的視窗作業系統(Windows)搭載的網頁瀏覽器—Internet Explorer的排版引擎的名稱,它的第一個版本隨著1997年10月Internet Explorer第四版釋出,之後不斷的加入新的技術並隨著新版本的Internet Explorer釋出。在未來最新的Internet Explorer第七版中,微軟將對Trident排版引擎做了的重大的變動,除了加入新的技術之外,並增加對網頁標準的支援。儘管這些變動已經在相當大的程度上落後了其它的排版引擎。使用該引擎的主要瀏覽器:IE,TheWorld,MiniIE,Maxthon,騰訊TT瀏覽器。事實上,這些瀏覽器是直接使用了IE核心,因為其userAgent字串中返回的資訊與IE是一模一樣的!

  2. Gecko

    壁虎,英文為"Gecko"。Gecko是由Mozilla基金會開發的佈局引擎的名字。它原本叫作NGLayout。Gecko的作用是讀取諸如HTML、CSS、XUL和JavaScript等的網頁內容,並呈現到使用者螢幕或列印出來。Gecko已經被許多應用程式所使用,包括若干瀏覽器,例如Firefox、Mozilla Suite、Camino,Seamonkey等等

  3. Presto

    Presto是一個由Opera Software開發的瀏覽器排版引擎,供Opera 7.0及以上使用。Presto取代了舊版Opera 4至6版本使用的Elektra排版引擎,包括加入動態功能,例如網頁或其部分可隨著DOM及Script語法的事件而重新排版。Presto在推出後不斷有更新版本推出,使不少錯誤得以修正,以及閱讀Javascript效能得以最佳化,併成為速度最快的引擎。

  4. KHTML

    是HTML網頁排版引擎之一,由KDE所開發。KDE系統自KDE2版起,在檔案及網頁瀏覽器使用了KHTML引擎。該引擎以C++程式語言所寫,並以LGPL授權,支援大多數網頁瀏覽標準。由於微軟的Internet Explorer的佔有率相當高,不少以FrontPage製作的網頁均包含只有IE才能讀取的非標準語法,為了使KHTML引擎可呈現的網頁達到最多,部分IE專屬的語法也一併支援。目前使用KHTML的瀏覽器有Safari和Google Chrome。而KHTML也產生了許多衍生品,如:WebKit,WebCore引擎

利用userAgent檢測

下面是各大瀏覽器使用彈窗顯示的userAgent字串

IE瀏覽器:Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727)

火狐瀏覽器:Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.0.4) Gecko/2008102920 Firefox/3.0.4

Opera瀏覽器:Opera/9.64 (Windows NT 5.1; U; Edition IBIS; zh-cn) Presto/2.1.1

Safari瀏覽器:Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN) AppleWebKit/528.16 (KHTML, like Gecko) Version/4.0 Safari/528.16

Google Chrome瀏覽器:Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/530.5 (KHTML, like Gecko) Chrome/2.0.172.33 Safari/530.5

可以使用下面的程式碼進行瀏覽器檢測

    var Browser = {
        isIE:navigator.userAgent.indexOf("MSIE")!=-1,
        isFF:navigator.userAgent.indexOf("Firefox")!=-1,
        isOpera:navigator.userAgent.indexOf("Opera")!=-1,
        isSafari:navigator.userAgent.indexOf("Safari")!=-1
    };

 

但這樣做並不是萬無一失的,一個特例便是Opera可以使用userAgent偽裝自己。下面是偽裝成IE的userAgent:Mozilla/5.0 (Windows NT 5.1; U; Edition IBIS; zh-cn; rv:1.8.1) Gecko/20061208 Firefox/2.0.0 Opera 9.64;在完全偽裝的情況下,最後的“Opera 9.64”這個字串也不會出現,但Opera也有特殊的識別自身的方法,它會自動宣告一個opera全域性變數!

不但如此,我們的檢測還忽略了一點,就是那些使用相同引擎而品牌不同的瀏覽器,所以,直接檢測瀏覽器是沒有必要的,檢測瀏覽器的解釋引擎才是有必要的!

    var Browser = {
        isIE:navigator.userAgent.indexOf("MSIE")>-1 && !window.opera,
        isGecko:navigator.userAgent.indexOf("Gecko")>-1 && !window.opera 
        && navigator.userAgent.indexOf("KHTML") ==-1,
        isKHTML:navigator.userAgent.indexOf("KHTML")>-1,
        isOpera:navigator.userAgent.indexOf("Opera")>-1
    };

 

物件檢測

瀏覽器檢測就到此結束了,下面應該講一下物件檢測!物件檢測其實是比瀏覽器檢測更加有效更加科學方法,而且我們之前一直在使用!

    function addEvent(obj,evtype,bubbles) {
        if (obj.addEventListener) {....}
        else if (obj.attachEventListener) {....}
    }

 

物件檢測避免了瀏覽器引擎的多樣性,即當我們需要某種功能時,我們直接檢測瀏覽器是否支援該功能,而不用管瀏覽器是什麼牌子的!

什麼時候該使用瀏覽器檢測?什麼時候該使用物件檢測?

答案是能使用物件檢測時總該使用物件檢測,只有當必須對瀏覽器進行識別或無法使用物件檢測時才進行userAgent判斷

    //一段用於將當前頁面新增到使用者收藏夾的程式碼,兩個不同的版本
    window.external.addFavorite(location,"收藏頁面");//IE
    window.sidebar.addPanel("收藏頁面",location,"");//火狐
    //由於在火狐下window也具有external屬性,並且在IE下判斷window.external.addFavorite會出錯
    if (window.external.addFavorite) {...}//程式碼在IE下會出錯
    //可以使用瀏覽器檢測,避免意外
    if (Browser.isIE) {
        window.external.addFavorite(location,"收藏頁面");//IE
    } else if (Browser.isGecko) {
        window.sidebar.addPanel("收藏頁面",location,"");//火狐及其它相同引擎的瀏覽器
    }

 

當你的指令碼和其它指令碼一起工作時(尤其和那些有問題的指令碼),有時候需要同時使用物件檢測與瀏覽器檢測

    //對於window.innerWidth這些屬性,可能有些指令碼會建立一個相容多個瀏覽器的同名屬性
    if (isIE) {//它聰明的使用了瀏覽器檢測
        window.onresize = function () {
            window.innerWidth =document.documentElement.clientWidth;
            window.innerHeight = document.documentElement.clientHeight;
        };
        window.onresize();
    }
    //然而我們的指令碼對其進行檢測時就麻煩了
    if (window.innerWidth) {//僅當在FF下時(FF支援position:fixed)才這樣做
        obj.style.position="fixed";
        obj.style.right=window.innerWidth/2+"px";
        obj.style.bottom=window.innerWidth/2+"px";
        ....
    }

 

當然,使用瀏覽器檢測始終是困難重重的,而對於測試文件是否支援某種特性,使用物件檢測可能是最有效的,還有另一種方法可以直接測試瀏覽器是否支援某標準!

測試與DOM標準的一致性

document物件有個implementation屬性,該屬性只有一個方法hasFeature(),用來測試瀏覽器是否支援某個DOM標準!

    //測試是否支援XML DOM 1.0
    var supportXML = document.implementation.hasFeature("XML", "1.0");

 

特 徵支援的版本描 述
Core 1.0, 2.0, 3.0 基本的DOM,給予了用層次樹來表示文件的能力
XML 1.0,2.0,3.0 核心的XML擴充套件,增加了對CDATA Section、處理指令和實體的支援
HTML 1.0,2.0 XML的HTML擴充套件,增加了對HTML特定元素和實體的支援
Views 2.0 基於特定樣式完成對文件的格式化
StyleSheets 2.0 為文件關聯樣式表
CSS 2.0 支援級聯樣式表1(CSS Level 1)
CSS2 2.0 支援級聯樣式表2(CSS Level 2)
Events 2.0 通用DOM事件
UIEvents 2.0 使用者介面事件
MouseEvents 2.0 由滑鼠引起的事件(點選、滑鼠經過,等等)
MutationEvents 2.0 當DOM樹發生改變時引發的事件
HTMLEvents 2.0 HTML 4.01的事件
Range 2.0 操作DOM樹中某個特定範圍的物件和方法
Traversal 2.0 遍歷DOM樹的方法
LS 3.0 在檔案和DOM樹之間同步地載入和儲存
LS-Async 3.0 在檔案和DOM樹之間非同步地載入和儲存
Validation 3.0 用於修改DOM樹之後仍然保持其有效性的方法

儘管這個相當方便,但是,使用implementation.hasFeature()有其明顯的缺陷——決定DOM實現是否對DOM標準的不同的部分相一致的,正是去進行實現的人(或公司)。要讓這個方法對於任何值都返回true,那是很簡單的,但這並不一定表示這個DOM實現真的和所有的標準都一致了。目前為止,最精確的瀏覽器要數Mozilla,但它多少也有一些並不完全和DOM標準一致的地方,這個方法卻返回為true。

錯誤處理

無盡的DOM相容性問題,如果總是使用物件檢測,就會帶來無盡的if else之類的分支語句,而且檢測某些物件的某些屬性時還會引發錯誤(尤其是在IE下),因為一般的JavaScript物件都可以轉換成布林值,但瀏覽器內建的一些方法或物件並不是JavaScript建立了,它們不一定能夠轉換成布林值!所以,使用錯誤處理語句不但避免了分支判斷,而且可以很優雅的處理錯誤!

try {} catch(e) {} finally {}

    function addFav(address,name) {
        try {
            window.sidebar.addPanel(name,address,"");//FF方法
            //在try語句中,如果指令碼出錯,會自動跳轉到catch語句執行
        } catch(e) {//這裡的e是必須的,是語法的一部分,它表示一個錯誤物件
            window.external.addFavorite(address,name);//IE
            //如果在這裡還出現錯誤,瀏覽器就會將這個錯誤丟擲
        }
    }

 

Error物件

JavaScript內建了一個Error建構函式,可以建立一個錯誤物件,並可以使用throw語句手動丟擲錯誤!

    var err = new Error();
    throw err;
    //在IE中,會在錯誤視窗中顯示“未指明的錯誤”,而FF中則是空字串
    //丟擲錯誤後,指令碼便會停止往下執行
    var message = "我丟擲的錯誤!";//錯誤描述
    err = new Error(message);
    throw err;

 

錯誤物件的屬性:message屬性儲存與錯誤物件相關的描述文字,number物件儲存錯誤程式碼。(錯誤號是 32 位的值。高 16 位字是裝置程式碼,而低字是實際的錯誤程式碼,不過錯誤程式碼對我們來說是沒什麼意義的)

        try {
            undefined();//當try語句中指令碼出現錯誤時,會自動丟擲一個錯誤物件
        } catch(e) {//e是自動建立是區域性變數,是Error的例項
            alert("錯誤資訊是:"+e.message+"\n"+"錯誤程式碼是:"+e.number);
        } finally {
            //finally語句不管出現不出現錯誤,都將執行,一般用於出錯時釋放物件
        }

 

onerror事件處理

DOM還有個onerror事件處理,當頁面載入出錯,圖象載入出錯及頁面指令碼出錯時會執行註冊的事件處理函式,一個常用的方法便是,在事件處理函式中返回true可以阻止瀏覽器出現錯誤提示!

    window.onerror = function () {
        alert("出錯時你會看到我的!");
        return true;
    };

 

另外一個用法是,可以監測影象的載入,如果影象載入出錯,可以重新載入影象(重新設定src屬性),也可以利用這個方法來測試一下影象是否存在(比如檢測使用者輸入的遠端影象的URL是否有效)

儘管使用try {} catch(e) {}語句,及使用onerror事件處理可以處理指令碼執行時錯誤,但它們是不能處理語法錯誤的!另外,使用錯誤處理語句並不是為了隱匿錯誤,而只是為了不干擾指令碼的繼續執行!

記錄錯誤

處理了這麼多錯誤後,記錄錯誤這樣的事情其實已經做過很多次了,主要有下面幾種方法

  • 使用alert語句,好處是可以讓指令碼暫停執行,壞處便是彈窗不那麼好控制
  • 使用document.write方法,這種方法自然避免了彈窗沒法關閉的情況,但不好控制指令碼執行!另一點便是,使用document.write會清空當前頁面!
  • 與document.write方法類似的是使用一個自建的控制檯(比如一個DIV),然後將錯誤資訊一條一條的新增進去,這種方法肯定比document.write好多了!
  • 還有另一種方法便是使用瀏覽器內建的控制檯,如在JavaScript裡面可以呼叫Java控制檯並向其中寫入資訊(前提是安裝了JRE)
    java.lang.System.out.println("錯誤資訊!");//使用Java控制檯
    console.log("錯誤資訊!");//使用火狐的控制檯
    opera.postError("錯誤資訊!");//使用Opera的控制檯

 

使用庫提高開發效率

庫,就是一些可以方便的應用到當前的開發體系中的程式碼資源,JavaScript庫又被稱之為JavaScript框架,它是由一些類和普通函式構成。使用庫,可以使開發者不必關心程式的實現細節而只專心於業務邏輯。有很多流行的庫,它們大都能夠跨瀏覽器工作,並且採用了物件導向的良好編碼方式。 事實上,庫就是將之前我們寫的那些可以跨瀏覽器執行的函式集中到一個JS檔案中,以便能在所有頁面都能夠重複利用這些程式碼!當然,它們不是簡簡單單的將函式放到一起!下面是一些流行的庫及其優缺點:

  • Prototype

    不要把它和JavaScript裡面的prototype相混淆,Prototype是一個JS框架(官方稱之為框架),可以說是最早也是最出名的JS框架了。 它提供了許多JS物件導向的擴充套件及DOM操作API,之前它一直由於缺乏API文件而備受詬病,但現在其文件已很充足。 它的優點顯而易見,它只提供一些核心的,底層的功能,所以程式碼精簡,體積較小,易學易用,但由於其只具有底層功能,往往需要協同其它UI庫來執行! 目前,基於Prototype的庫已經有很多,著名的有整合Prototype庫的RoR Ajax庫,以及為Prototype提供許多視覺特效的Scriptaculous庫。

  • jQuery

    jQuery是一款同Prototype一樣優秀js開發庫,特別是對css和XPath的支援,使我們寫js變得更加方便!如果你不是個js高手又想寫出優秀的js效果,jQuery可以幫你達到目的!並且簡潔的語法和高效率一直是jQuery追求的目標。(其官網標語:jQuery將改變您書寫JavaScript的方式!)。 連YAHOO-UI都重用了很多jQuery的函式。支援外掛是jQuery的另一大優點,可以無即的擴充套件其功能。其缺點便是內部結構複雜,程式碼較為晦澀,一般的新手根本無法看懂其原始碼。所以jQuery適合開發而不適合一個剛開始學習建立JS庫的新手研究其原始碼。

  • EXT-JS

    EXT-JS前身是YAHOO-UI,EXT-JS是具有CS風格的Web使用者介面元件 能實現複雜的Layout佈局,介面效果可以和BackBase(另一JS庫)媲美,而且使用純javascript程式碼開發。真正的可編輯的表格Edit Grid,支援XML和Json資料型別,直接可以遷入grid。許多元件實現了對資料來源的支援,例如動態的佈局,可編輯的表格控制元件,動態載入的Tree 控制元件、動態拖拽效果等等。1.0 beta版開始同Jquery合作,推出基於jQuery的Ext 1.0,提供了更多有趣的功能。 對於一個喜歡JAVA的開發者來說,EXT-JS類似於java的結構,清晰明瞭,另一特點其可以實現華麗的令人震撼的WEB應用程式。當然,缺點也很嚴重,由於很多HTML及CSS程式碼都是EXT-JS自已建立的,所以其介面構造十分複雜,沒有讓我們自己寫CSS的餘地。

  • Dojo

    Dojo是目前最為強大的工業級JS框架。它在自己的Wiki上給自己下了一個定義,dojo是一個用JavaScript編寫的開源的DHTML工具箱。dojo很想做一個“大一統”的 工具箱,不僅僅是瀏覽器層面的,野心還是很大的。Dojo包括ajax, browser, event, widget等跨瀏覽器API,包括了JS本身的語言擴充套件,以及各個方面的工具類庫,和比較完善的UI元件庫,也被廣泛 應用在很多專案中,他的UI元件的特點是通過給html標籤增加tag的方式進行擴充套件,而不是通過寫JS來生成,dojo的API模仿Java類庫的組織 方式。 用dojo寫Web OS可謂非常方便。dojo現在已經4.0了,dojo強大的地方在於介面和特效的封裝,可以讓開發者快速構建一些相容標準的介面。 Dojo幾乎集了成了上面的JS庫的優點,其功能非同一般的強大,已得到了IBM和SUN的支援,但是自然的,其體積也十分大,總共有200多KB,另外,其語法也不如jQuery靈活,對JavaScript語言的增強也不如Prototype。

  • Scriptaculous

    Scriptaculous是基於prototype.js框架的JS效果。包含了6個js檔案,不同的檔案對應不同的js效果,所以說,如果底層用 prototype的話,做js效果用Scriptaculous那是再合適不過的了。優點便是基於Prototype,可以說是Prototype的外掛,不同的效果用不同的JS檔案分開存放,當然,依賴於Prototype也是其缺點。

  • moo.fx

    moo.fx是一個超級輕量級的javascript特效庫(3k),能夠與prototype.js框架一起使用。它非常快、易於使用、跨瀏覽器、符合標準,提供控制和修改任何HTML元素的CSS屬性,包括顏色。它內建檢查器能夠防止使用者通過多次或瘋狂點選來破壞效果。moo.fx整體採用模組化設計,所以可以在它的基礎上開發你需要的任何特效。輕量是其最大的優點,當然其缺點便是輕量級的,但是輕量級的JS庫能有如此強大已經很不錯了。

名稱空間

在建立庫之前,第一個要考慮的問題便是如何防止變數重名的問題!解決方案便是使用名稱空間!名稱空間是JAVA等語言中的一個概念,JavaScript並不支援名稱空間,但由於JavaScript的靈活性,我們可以使用物件來模似名稱空間!

    var NameSpace={};
    NameSpace.fn1 =function () {};
    NameSpace.fn2 =function () {};

 

現在,fn1與fn2兩個函式就同屬於NameSpace名稱空間中了,它們不會也全域性中的fn1或fn2衝突,當然,在使用的時候必需加上NameSpace字首。這樣使用名稱空間並不是最簡單的,因為我們不得不在任何地方呼叫這些函式的時候都加上NameSpace字首。下面是使用JavaScript閉包來解決的方案:

    var NameSpace={};
    (function () {
        function fn1() {
        }
        function fn2() {
            fn1();//在這裡呼叫fn1不需要加NameSpace字首
        }
        NameSpace.fn1=fn1;//將其新增為全域性變數NameSpace的屬性,以便能在函式外訪問
        NameSpace.fn2=fn2;
    })();

 

相關文章