javascript快速入门21--DOM总结

跨浏览器开发

市场上的浏览器种类多的不计其数,它们的解释引擎各不相同,期待所有浏览器都一致的支持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;
    })();