💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
# 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) ![](https://box.kancloud.cn/2016-05-17_573b067429c78.png) 火狐浏览器:Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.0.4) Gecko/2008102920 Firefox/3.0.4 ![](https://box.kancloud.cn/2016-05-17_573b06743a2a9.png) Opera浏览器:Opera/9.64 (Windows NT 5.1; U; Edition IBIS; zh-cn) Presto/2.1.1 ![](https://box.kancloud.cn/2016-05-17_573b06744b6b1.png) Safari浏览器:Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN) AppleWebKit/528.16 (KHTML, like Gecko) Version/4.0 Safari/528.16 ![](https://box.kancloud.cn/2016-05-17_573b067461579.png) 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 ![](https://box.kancloud.cn/2016-05-17_573b06747a3fb.png) 可以使用下面的代码进行浏览器检测 ``` 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; })(); ```