Archive for the ‘前端技术’ tag
前端代码的本地化开发
如果网站的后台是PHP或者JAVA的,那么搭建本地开发环境并不是什么难事,可是由于我们大部分站点的后台语言是C++写的cgi程序,服务器是Linux,所以cgi都是在Linux下编译的,而我们的开发机器是windows,因此对我们来讲,搭建本地开发环境比较困难。所以一直以来都是所有的前端开发人员都是在一个由samba搭建的Linux共享磁盘里面进行开发。
随着团队人数的增加,多人在共享磁盘里面开发的弊端越来越明显。首先是代码很难用版本控制工具进行管理,由于samba的效率问题,在多人使用的samba共享目录里面操作SVN非常痛苦,更新和提交操作的速度很慢,经常卡死,后来不得已直接取消了在开发环境用SVN进行版本管理。但是没有版本管理之后很容易造成代码的丢失或者被他人覆盖,毕竟是多人共用的磁盘,出现任何情况都有可能,所以经常要手动备份代码,这对于大部分开发人员来说是一个痛苦的过程。共享磁盘开发的另一个弊端是,有些同事开发了一个功能只完成了一半导致流程中断不能继续下去,妨碍其他同事的工作。
所以最近我们又开始讨论本地化开发的问题,如果采用本地开发就可以大体上解决上面的两个问题。在本机使用版本管理,代码更容易追踪,谁在什么时间修改了什么代码可以看得一目了然。经过团队内部的讨论得到如下基本可行的本地开发方案:
所需要的工具和前提条件:
1.TortoiseSVN和一个前端代码的版本库
2.Plink及开发服务器的SSH账号
3.Fiddler + Willow(感谢YuniShi)
4.本机安装Apache
我们前端代码基本上都在htdocs和template两个目录里面。htdocs里面主要是静态HTML、CSS,JS和图片等,当然HTML不能算全静态的,其中用到了Apache的SSI指令。template目录里面全部是cgi的模版文件。
本机安装好Apache之后,将前端代码(htdocs和template)checkout到本机,htdocs设置为Apache的根目录,由于htdocs里面都是静态文件,只要Apache开启了SSI指令,完全可以通过本机的Apache来访问。所以配host www.domain.com 127.0.0.1即可。
template里面的代码可以必须通过服务器的cgi来解析,因此不可能通过本机的Apache来访问。解决方法是在开发服务器上checkout一份前端代码的工作副本,将所有的cgi请求发送到服务器,服务器上的cgi解析的也是服务器上的template里面的模版。这里会有两个问题:一、访问静态文件的域名和访问cgi的域名是同一个,如何将静态文件请求发送到本机Apache而将cgi的请求发送到服务器?这个时候Fiddler + Willow就派上了用场,由于所有的cgi都在/cgi-bin/这个目录中,通过Willow的针对文件夹配host的功能就可以将所有cgi的请求发送到linux开发服务器。下图中通过Willow的Host list将www.tenpay.com和img.tenpay.com两个域名指向本机ip:127.0.0.1,然后通过一个extension将所有/cgi-bin/目录的hosts配置到开发服务器:
第二个问题是cgi访问的是linux服务器上的template目录的模版文件,如何谈得上是本地开发?我们修改的其实是本地的模版文件然后通过SVN钩子自动同步到服务器上。原理是这样的:我们给本机的SVN工作副本设置一个post-commit钩子,就是一个批处理文件,我们修改完模版文件需要看效果的时候就做一次SVN提交操作,提交操作会触发这个post-commit钩子,而这个钩子的作用就是通过Plink的SSH连接到服务器并更新服务器上的SVN工作副本。SVN钩子的设置方法见下图:
钩子设置完毕之后整个设置过程就OK了,这个时候在浏览器打开www.domain.com(开启Fiddler)会发现所有的静态文件都请求了本机的服务器,而所有的cgi请求发送到了开发服务器,如果修改静态文件,直接刷新浏览器就可以看效果,如果修改了模版文件则需要提交一下SVN再刷新看效果。
优化HTML
为何要保持标签整洁
客户端的优化近来倍受关注,可是有些较基本的方面却被忽视。如果你仔细观察某些页面(即便是那些本来应该深度优化的页面),很容易就能在他们的标签中找到一大堆冗余的、不高效的结构。所有这些累赘给本来应该尽可能保持轻量级的页面增加了不必要的负担。
保持文档整洁的原因不一定是为了更快的加载速度,更是为了让我们的建筑能有一个结实而牢固的基础,整洁的标记意味着更好的可访问性,更方便的维护,更易被搜索引擎检索,更小的体积仅仅是保持文档整洁的一个附加属性,也是我们应该这样做的另一个理由。
这篇文章里面,我们来瞧瞧html该如何优化:去掉一些不好的标记习惯、通过删除冗余的结构来减小文档体积,并应用压缩技术,我们来瞧瞧现有的压缩工具,并分析他们做得好和做得不好的地方,我们也会探讨一下在将来可以怎么做。
一团糟的写法
来看看有哪些最容易犯的错误
1.在script标签内放html注释
当今一个最严重的冗余代码是在script标签内放置HTML注释——<!-- -->,这里不需要说得太多,一句话足够:需要通过这种方式来阻止错误的浏览器(例如‘95 Netscape 1.0)几乎灭绝了,脚本内的注释是完全不必要的累赘,应该被毫不留情地删除掉。
2.<![CDATA[ … ]>块
另外一种常见但是不必要的阻止错误的方式是将CDATA块放到script标签内:
<script type="text/javascript">
//<![CDATA[
...
//]]>
</script>
这个神圣的目标不切实际。尽管CDATA块是一个阻止XML解析器将<和&当成标签的开头的一个很好的方式,但是只有在真正的XHTML文档(被当成“application/xhtml+xml” content-type来处理的文档)内才有效。目前大部分的网页仍然被当成“text/html”来处理的(因为,举个例子,IE目前仍然不支持 XHTML),所以他们都被当成HTML来解析,而不是XML。
除非你已经把文档当成"application/xhtml+xml"来处理,否则几乎没有任何理由把CDATA块放在里面,即便你打算以后使用XHTML,也有必要先把这些不必要的东西去除,等到真正需要的时候再加进来。
当然,一个终极的解决方案是完全避免使用内联的script标签(为了利用外部script的缓存机制)。
3.类似onclick="…", onmouseover=""这些
html事件属性有一些合理的应用场景,比如为了性能上的考虑或者为了应付古老的浏览器(尽管我还没有发现只支持html事件属性注册方式——onclick="..."而不支持dom节点属性注册方式element.onclick = ...的情况)。除了一些众所周知的拒绝它们的理由如行为和内容的分离或者便于重用,它还会污染html标记。把事件逻辑转移到外部的脚本里面,我们可以充分利用脚本的缓存机制。事件处理逻辑不必在每次的页面请求中都被传送到客户端。
4.onclick="javascript:…"
javascript 中一个很有趣的怪现象:javascript:伪协议和html事件属性处理器混合成了这样一个怪胎(发生的几率高达106,000 (!)),事实上html属性上的事件处理器会整体变成一个函数体,然后这个函数变成一个事件处理程序(通常,会先把函数的作用域加强来包含元素本身和它的部分或全部的父级)。“javascript:” 这部分变成了一个不必要的标签并且几乎没有任何作用。
5. href="javascript:void(0)"
继续javascript伪协议,有一个很著名的代码片段:href=”javascript:void(0)”,他的作用是阻止链接的默认行为。当javascript被禁用、不可用或出错的时候这个糟糕的写法使得链接完全不可访问。毫无疑问最理想的处理方法是将合适的url写入href里面,然后通过事件处理器来阻止链接的默认行为。另一种情况,如果链接是动态创建之后插入到页面中(或者最初是隐藏的,然后才通过 javascript让其显示),使用href=”#”比用javascript:更整洁,更快速。
6.style="…"
使用style属性并没有什么太严重的错误,只是如果把它里面的内容转移到一个外部的样式表里面,我们就能利用到源代码的缓存机制。这跟前面提到过的 html属性事件处理器类似。即便你只是为了给一个特定的元素设定样式并且没有打算重用它们,这些样式信息会在每次页面被请求的时候都传送一次。把样式转移到外部样式表里面能防止这个,因为外部样式表传送一次之后会缓存到客户端。
7.<script language=”Javascript” … >
也许一个最搞不懂的属性就是script标签的language属性,这个古老的属性早在1999年、10年前官方推荐HTML 4.01标准的时候就被抛弃了,除非在必须指定javascript版本(这个也不太可靠,应该尽可能避免)这种极少的情况下,否则没有任何理由继续使用这个属性。
8.<script charset=”…” … >
另一个搞不懂的属性是script的charset属性,有时候我会见到下面这种结构:
<script type="text/javascript" charset="UTF-8">
...
</script>
这里要说的是charset属性只放在“外部”script标签(有写src 属性的script标签)上才有意义。HTML 4.01 甚至说:
注意charset属性指定了src属性所指定的脚本的字符编码;它不关心script标签的内容。
测试表明浏览器都遵循了这条标准。
搜索一下这个结构,出现了2000次。当你发现像Textmate这些流行的编辑器里面都错误地使用了charset之后就一点都不觉得奇怪了。
9. 附加的优化方法
刚才我们说到了一些应该避免的不好的写法,但是还有一些,那就是删除冗余的部分。下面提到的一些优化是有些争议的,因为他们主要出于体积上的考虑。所以我把它们放在这里不是作为建议,而是作为一种选择。请各位三思而行。
1. <style media=”all” …>
HTML 4.01定义了style上的media属性,用来指定特定的媒介——屏幕、打印机、手持设备等等。media的一个可能的值是all,这个值恰好是现代浏览器的默认值,如果你发现自己在使用media="all",那么你可以省去它,让浏览器自动帮你设置
有趣的是,HTML 4.01说media的默认值是"screen"。但是我测试的每一款浏览器都没有按照这条标准来执行,而把默认值设定成了"all"。这可能就是为什么HTML5草案把默认值指定为"all",来和浏览器的行为达成一致。
2.<form method=”get” …>
另一个默认值——GET——form 的"method"属性常常是被明确设定的。其实除了不够清晰,省去它没有其他坏处。注意HTML 5草案没有更改这个行为。
3. <input type=”text” …>
INPUT元素的type属性默认值为"text"——在HTML 4.01和HTML 5草案里面都是如此。去掉这个属性对有很多文本输入框的页面来说能减少很大一部分体积。
4.<meta http-equiv=”Content-type” …>
指定页面的字符编码总是给人很大的困惑。和我们想当然的相反,META元素上指定的Content-type没有 HTTP头里面指定的“Content-type”的优先级高。当二者(HTTP头和META元素)都被设定的情况下,HTTP头优先。
如果你能控制服务器返回并且能够正确地设定HTTP头的Content-type,那么省略META标签也是可以的。保留它的唯一原因是为离线浏览该页面的时候指定编码。
5.<a id=”…” name=”…” …>
把”name”属性和”id”属性一起使用的主要原因是为了兼容古老的浏览器(如Netscape 4)。他们不能链接到用“id”指定的锚点,所以必须设置”name”,如果你的页面有有元素既设置了”id”又设置了”name”,并且不在意这些古老的浏览器,那么你可以摆脱这个古老的写法了。
注意一些副作用。如果你在脚本中通过name来查询元素(document.getElementsByName,document.evaluate,document.querySelectorAll等等)把name替换成id可能会把事情搞砸。还要记住document.anchors只返回带有name属性的元素。
6.<!doctype html>
一年多以前,Dustin Diaz建议使用HTML 5文档类型,作为减少页面体积的一个途径。这不是一个重要的优化方式,但是如果你不在乎是否通过检验并且需要使页面尽可能小巧,使用<!doctype html>是一个可行的备选方案。测试表明这个奇特的文档声明可以触发大量的浏览器的标准模式。
激进的优化方法
如果你渴望更多的,这里还有一些比较极端的方法。有一些(如略去选填的标签)已经出现过一段时间了,还有些之前没出现过。尽管这些做法看起来有些唐突,但是它们都可以通过检验,如果你的页面是HTML的而不是XHTML的。但是你的页面是HTML的,不是吗?;)
- 去掉HTML注释
- 去掉/减少空格
- 省去选填的结束标签(
<p>→<p>foo) - 如果允许,去掉属性值的引号 (
<p class="foo">→<p class=foo>) - 删除布尔类型的属性的选填值(
<option selected="selected">→<option selected>) - 转移内联样式、内联脚本、和html事件属性 (如果不能删掉它们的话)
- 转移class和id(需要同步修改脚本和样式)
- 把URL的协议名称去掉 (
http://example.com→//example.com)
但我们可以压缩
当页面压缩之后这些优化还有意义吗?难道gzip不是已经把这些冗余的标记都搞定了吗?毕竟,我们说的是一个文本格式的东西。
用处还是有的!
首先,必须清楚不是每个人都能gzip,这是很悲哀的事情,但好的一面是在这种情况下HTML 优化的意义就更大了。
其次,即便页面压缩了,我们还可以节省5-10kb(平均每个页面)。在比较大的页面中还可以节省得更多。尽管看上去也没有太多,但实际上每一个字节都很重要。
作为一个压缩大型页面的例子,我优化了一下非官方的ECMA-262, 第三版规范的HTML版本,这个页面最初为750KB(gzip之后为131KB),优化之后为606KB(gzip之后115KB)。也就是说gzip之后节省了16KB,仅仅是去掉空格、注释、属性值的引号、选填标签。你可以看看这个压缩后的版本和以前的版本看起来是一模一样的。
最后,像去除空格和注释这样的优化能使得文档树更轻量,潜在地增强了页面的渲染效率。
物极必反
像其他一些优化一样,我们很容易矫枉过正。HTML Compact 就是HTML压缩工具里面做得太过的一个例子,这个优秀的windows程序用了一种很“独特”的方式来压缩HTML——用javascript把内容写入到页面中。
把这个完美的页面:
<html>
<head>
<title></title>
</head>
<body>
<div>
<ul>
<li>foo</li><li>bar</li><li>baz</li>
<!-- few more dozens of list elements ... -->
</ul>
</div>
</body>
</html>
变成这副模样:
<!--hcpage status="compressed"-->
<html>
<head>
<SCRIPT LANGUAGE="JavaScript" SRC="hc_decoder.js"></SCRIPT>
<title></title>
</head>
<BODY>
<NOSCRIPT>To display this page you need a browser with JavaScript support.</NOSCRIPT>
<SCRIPT LANGUAGE="JavaScript">
<!--
hc_d0("Mv#d|\x3C:,&c@w4YFAtD1 [... and so on, another couple hundreds of characters ...]");
//-->
</SCRIPT>
</BODY>
</html>
不用说,像这样的“优化”永远不应该在网络上出现,除非你的目的是降低页面对用户和搜索引擎的可访问性。并且里面的NOSCRIPT标签也让人很不爽,这种东西对某些能阻止javascript的防火墙来说也是不起作用的。馊主意,坏做法!
上面是一个很好的反例,但是还有一些也是应该注意的:
反面教材
1. 去除文档声明
HTML Compresor里面有一个选项——默认是开启的——去除文档声明。我实在想不出这么做会有什么的好处,相反,没有文档类型会激活怪异模式,使页面的布局和行为都出现很严重的问题。文档类型要么放着不动,要么被替换为一个更简短的——HTML5声明。
2.把STRONG用B替代,EM用I替代
还是那个HTML压缩工具里面有一个有害的选项是把元素用比他们更短的“替代品”来替换。问题是B不是STRONG的替代品。I也不是EM的替代品。 STRONG和EM是有语义的——表示强调,而B和I只是去改变字体样式的;他们能改变文字的展现,但是没有任何语义。
虽然浏览器把他们显示成一样的,但是屏幕阅读器和搜索引擎知道他们的区别。
3.去除title,alt属性和label元素。
一条比较重要的原则是如果优化会损失可访问性就不要优化。你可能很想删掉IMG上的“alt”属性,或者链接上的“title”属性,但是节省少量的字节相对于损失的可访问性来说的确是得不偿失。
工具
使“附加优化方法”里面的优化自动化是比较琐碎的事情。已经有一些工具可以去除注释、空格和属性值的引号,但这些工具仍然是比较初级的,它们做的优化还比较有限,我们当然能做得更好:
几个月以前,hakunin 和我都开始做一个很相似的基于Ruby的压缩工具,但都没有完成。
目前来说我们可以用的:
1.Absolute HTML Compressor (桌面程序, windows)
如果你把去除文档类型和用I替换STRONG的选项去掉的话,它做得很不错。
2.HTML Compact (桌面程序, windows)
降低页面的可访问性。应避免使用。
3.HTML Compressor (桌面程序, windows)
只是删除空格,甚至把对空格敏感的元素如PRE里面的空格也删除了。用处不大。
4.Pretty Diff (基于网页)
没有选项可以完全的删除空格(只是减少空格)。除了减少空格和并成一行外不做其他的优化,也不能正确处理对空格敏感的元素。用处不大。
5.htmlcompressor (基于java)
这里提到的优化它大部分可以完成(但是不删除选填标签或缩短布尔类型的属性),能正确处理对空格敏感的元素,也许是目前最好的一个选择
可以看到,现在的状况是很令人失望的。好像没有Mac/Linux下的压缩工具,windows下的用处又不大。
将来的考虑
虽然转移或者删减可以(并且应该)在产品成型的时候来处理,但是一团糟的写法压根就不应该出现,不管是成品还是开发阶段,没有任何例外。
最好的优化常常还是人工来处理的:更改页面结构来避免多个元素上重复的class(把它们放到父级元素上去)、去除不是立即要用的内容,将他们动态加载。替换掉不高效的用于展现目的的<br>和 ,或替掉换基于表格的布局也是人工优化很好的例子。
就这些优化工具而言,我希望在不久的将来看到更多的压缩工具,将页面体积缩减推向一个新的高度。
如果你知道更多优化HTML的方法,请分享出来。很乐意大家提问,提建议或者纠正我的错误。
本文翻译自Optimizing HTML
this关键字
javascript最重要的关键字之一是this。但是如果你不了解它的工作原理使用起来就会很困难。
下面我将介绍在事件处理中如何运用它。以后我会增加this的其他用途。
所有者
这篇文章将要讨论的问题是:在函数doSomething()中this到底指向谁?
function doSomething() {
this.style.color = '#cc0000';
}
在javascript中,this始终指向我们正在执行的这个函数的“所有者”,或者更确切地说,函数是哪个对象的方法this就指向哪个对象。当我们在页面中定义函数doSomething()的时候,他的所有者就是这个页面,或者说javascript的window对象(或全局对象)。而一个onclick属性的所有者是它所在的HTML元素。
这种“所属关系”是javascript的面向对象特性造成的,欲了解更多信息请阅读这篇对象亦是关联数组。
------------ window -------------------------------------- | / \ | | | | | this | | ---------------- | | | | HTML 元素 | <-- this ----------------- | | ---------------- | | doSomething() | | | | | ----------------- | | -------------------- | | | onclick 属性 | | | -------------------- | | | ----------------------------------------------------------
如果我们直接执行doSomething(),那么this关键字指向window,并且函数会去改变window对象的style.color,由于window对象没有style属性,这个函数会执行失败,并产生js错误。
拷贝
如果我们要合理地运用this就必须确保使用它的函数为正确的HTML元素所拥有,或换一种说法,我们必须把函数拷贝到onclick属性上,传统的事件绑定模型很好地处理了这个问题。
element.onclick = doSomething;
函数被完整地拷贝到了onclick属性上(现在成了一个方法),所以当事件处理函数被执行的时候this指向HTML元素并且它的color会被改变。
------------ window -------------------------------------- | | | | | | | ---------------- | | | HTML元素 | <-- this ----------------- | | ---------------- | | doSomething() | | | | | ----------------- | | ----------------------- | | | |doSomething()的一份拷贝| <-- 拷贝函数 | | ----------------------- | | | ----------------------------------------------------------
这里有个小技巧是我们可以把函数拷贝到多个事件处理器上,每次this都能指向正确地HTML元素:
------------ window -------------------------------------- | | | | | | | ---------------- | | | HTML元素 | <-- this ----------------- | | ---------------- | | doSomething() | | | | | ----------------- | | ----------------------- | | | |doSomething()的一份拷贝| <-- 拷贝函数 | | ----------------------- | | | | | | ----------------------- | | | | 另一个HTML元素 | <-- this | | | ----------------------- | | | | | | | | | ----------------------- | | | |doSomething()的一份拷贝| <-- 拷贝函数 | | ----------------------- | | | ----------------------------------------------------------
这样才是比较充分地使用this,每次函数被执行的时候this都指向触发了这个事件的HTML元素,这个HTML元素“拥有”了doSomething()的一份拷贝。
调用
然而如果你使用行内事件绑定方式
<element onclick="doSomething()">
你没有拷贝函数!而是调用了这个函数。其中的区别是很关键的。这里的onclick属性并没有包含任何实际的函数,而只是一个触发了这个函数:
doSomething();
他只是说了句“找到doSomething()并执行它”,当我们找到doSomething()的时候this关键字再一次指向了全局的window对象,并且函数会返回错误提示。
------------ window -------------------------------------- | / \ | | | | | this | | ---------------- | | | | HTML元素 | <-- this ----------------- | | ---------------- | | doSomething() | | | | | ----------------- | | ----------------------- / \ | | | 找到doSomething() | | | | | 并执行它 | ---- 对函数的调用 | | ----------------------- | | | ----------------------------------------------------------
区别
如果你想通过this来获得触发了这个事件的HTML元素,就必须确保this关键字确实被写入到了onclick属性里面,只有这种情况下它才会指向该函数所绑定到的HTML元素。所以如果你测试下面这段
element.onclick = doSomething; alert(element.onclick)
你将得到:
function doSomething()
{
this.style.color = '#cc0000';
}
可以看到,this关键字出现在onclick方法里面,因此它会指向HTML元素。
但是如果你测试以下这段:
<element onclick="doSomething()"> alert(element.onclick)
会得到
function onclick()
{
doSomething()
}
这只是一个对函数doSomething()的调用,this关键字并没有出现在onclick方法里面,所以它不会指向HTML元素。
例子——拷贝
在下面的情况下this会写入onclick方法内:
element.onclick = doSomething
element.addEventListener('click',doSomething,false)
element.onclick = function () {this.style.color = '#cc0000';}
<element onclick="this.style.color = '#cc0000';">
例子——调用
下面这些情况this指向window:
element.onclick = function () {doSomething()}
element.attachEvent('onclick',doSomething)
<element onclick="doSomething()">
注意这里出现了attachEvent(),微软事件绑定模型的一个严重的缺点是attachEvent()只建立了对函数的调用但没有拷贝。所以有些时候我们无法知道是哪个HTML元素触发了这个事件。
二者结合
当使用行内事件绑定方式时也可以把this传给函数,使你仍然可以在函数内部使用它:
<element onclick="doSomething(this)">
function doSomething(obj) {
// this出现在了事件处理器内并且传给了函数
// obj现在指向HTML元素,所以你可以做以下操作
obj.style.color = '#cc0000';
}
本文翻译自PPK的The this keyword。
知名站点js库使用情况统计
| jQuery | Prototype | YUI | Mootools |
|---|---|---|---|
| 微软 新版MSN 诺基亚 美国在线 Google Code 维基百科 W3C 戴尔 IBM Digg Mozilla.org wordpress.org 以及wordpress博客后台 Netflix alexa.com ESPN 亚马逊 Box.net QQ校友 QQ群 改版后的新浪NBA频道 豆瓣 土豆 |
Apple CNN Ajaxian Adobe 37signals 开心网 优酷 |
Yahoo mozilla.com 美味书签 WebQQ 淘宝 支付宝 口碑网 阿里中文站 阿里国际站 沃尔玛 Paypal |
phpMyAdmin CNET 必应 wufoo.com Ask.com About.com |
开源js库在这些顶尖站点中的使用率如此之高,可见一定有其过人之处。顺便推荐火狐的Library Detactor插件,在firefox的状态栏显示正在访问的站点使用的是什么js库。
javascript的事件绑定函数
最早给DOM节点绑定事件处理函数的方法是onevent方式,例如:
function handler() {
// 函数内容略
}
var aaa = document.getElementById('aaa');
aaa.onclick = handler;
这种方式具有不错的兼容性,但是缺点是最多只能给一个元素绑定一个函数,后面绑定的函数会把前面绑定的函数覆盖掉。
2001年Scott Andrew LePera写了一个包裹函数addEvent()来解决事件绑定的问题,后来被开发人员广泛采用。但是这个函数略显复杂,ppk在《ppk谈javascript》里面推荐的两个函数是这个函数的简化版:
function addEventSimple(obj,evt,fn) {
if (obj.addEventListener)
obj.addEventListener(evt,fn,false);
else if (obj.attachEvent)
obj.attachEvent('on'+evt,fn);
}
function removeEventSimple(obj,evt,fn) {
if (obj.removeEventListener)
obj.removeEventListener(evt,fn,false);
else if (obj.detachEvent)
obj.detachEvent('on'+evt,fn);
}
这个函数解决了给一个元素绑定多个事件处理函数的问题,然而缺点是在ie里面是事件处理函数里面的this关键字不能指向所绑定的DOM对象,而是指向window对象,即作用域错误。为了解决这个问题ppk在2005年举办了一个addEvent()函数重构竞赛。竞赛的优胜者是jQuery的作者john Resig,他的函数如下:
function addEvent( obj, type, fn ) {
if ( obj.attachEvent ) {
obj['e'+type+fn] = fn;
obj[type+fn] = function(){obj['e'+type+fn]( window.event );}
obj.attachEvent( 'on'+type, obj[type+fn] );
} else
obj.addEventListener( type, fn, false );
}
function removeEvent( obj, type, fn ) {
if ( obj.detachEvent ) {
obj.detachEvent( 'on'+type, obj[type+fn] );
obj[type+fn] = null;
} else
obj.removeEventListener( type, fn, false );
}
这个函数修正了作用域,也有相配套的解除绑定函数,但是仍然有严重缺陷:他将字符串和函数相加以得到唯一的hash值,这种做法效率低下,并且不兼容某些手机浏览器。后来John Resig自己也说他不建议别人使用这两个函数(需要翻墙)。
在ppk的竞赛结束之后,作为评委的Dean Edwards也写了一个自己的addEvent()函数:
// written by Dean Edwards, 2005 // http://dean.edwards.name/ function addEvent(element, type, handler) { // 为每个事件处理程序分配一个唯一的id if (!handler.$$guid) handler.$$guid = addEvent.guid++; // 为该元素的各种事件类型创建一个hash表 if (!element.events) element.events = {}; // 为每一个元素/事件对的所有事件处理程序创建一个hash表 var handlers = element.events[type]; if (!handlers) { handlers = element.events[type] = {}; // 存储已经存在的事件处理程序(如果有的话) if (element["on" + type]) { handlers[0] = element["on" + type]; } } // 将事件处理程序存储到hash表内 handlers[handler.$$guid] = handler; // 剩下的任务交给一个全局的事件处理程序来搞定 element["on" + type] = handleEvent; }; // 一个用来分配唯一ID的计数器 addEvent.guid = 1; function removeEvent(element, type, handler) { // 从hash表里面删除该事件处理程序 if (element.events && element.events[type]) { delete element.events[type][handler.$$guid]; } }; function handleEvent(event) { var returnValue = true; // 取得event对象(IE使用了一个全局的事件对象) event = event || fixEvent(window.event); // 找到事件处理程序的hash表 var handlers = this.events[event.type]; // 执行各个事件处理程序 for (var i in handlers) { this.$$handleEvent = handlers[i]; if (this.$$handleEvent(event) === false) { returnValue = false; } } return returnValue; }; function fixEvent(event) { // 增加符合W3C标准的事件模型 event.preventDefault = fixEvent.preventDefault; event.stopPropagation = fixEvent.stopPropagation; return event; }; fixEvent.preventDefault = function() { this.returnValue = false; }; fixEvent.stopPropagation = function() { this.cancelBubble = true; };
Dean Edwards的这个方法是相对来说最完美的一个方案,没有使用addEventListener和attachEvent就实现了多重函数的绑定,并且事件处理函数支持this关键字。
上面提到的这些都有提供相配套的解除绑定函数,但是大部分时候我们只需要绑定,不需要解绑,这种情况下下面这个简单的函数已经足够满足我们的需要了。
function addEvent( obj, type, fn ) {
if (obj.addEventListener)
obj.addEventListener(type, fn, false);
else if (obj.attachEvent)
obj.attachEvent('on' + type, function() { return fn.call(obj, window.event);});
}
大部分开源的js框架,如jQuery和YUI都有提供很方便的事件绑定接口,其实现的方式就复杂多了,当然功能也要强大得多。等有时间再研究。
不用try catch创建XMLHttpRequest对象
一直搞不明白为什么许多书上在创建XMLHttpRequest对象的时候一定要用try catch方法来做,用对象检测方法不是要简单得多吗?今天看kkp on JavaScript看到Data Retrieval这一章时决定好好分析一下这个问题,首先用以下代码测试:
if (XMLHttpRequest) alert('supported!');
马上就有新的发现,原来IE 7内部已经有了XMLHttpRequest这个对象,此前还一直以为所有的IE都不支持XMLHttpRequest对象的,原来是我读的基本JS的书籍都太老了,根本没有提及IE7已经开始支持XHR这个问题。
刚才的代码在IE6里面会报错,提示XMLHttpRequest未定义,if语句有一个特点很有意思:当遇到未定义的变量的时候会报错,而遇到未定义的属性时则不会报错。在支持XHR的浏览器里面XMLHttpRequest可以看成是一个全局变量,而所有的全局变量同时也是window对象的属性,那么方法就出来了,在if语句里面把XMLHttpRequest写成window的属性,即if (window.XMLHttpRequest),这样IE6就不会报错了。
做了一个小小的搜索看快就发现原来IE的开发人员正是用对象检测来创建XHR对象的:
if (window.XMLHttpRequest) {
// If IE7, Mozilla, Safari, etc: Use native object
var xmlHttp = new XMLHttpRequest()
}
else if (window.ActiveXObject) {
// ...otherwise, use the ActiveX control for IE5.x and IE6
var xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
}
Update:projs中介绍一种更好的方法:
if ( typeof XMLHttpRequest == 'undefined' )
XMLHttpRequest = function() {
// Internet Explorer uses an ActiveXObject to create a new XMLHttpRequest object
return new ActiveXObject(
// IE 5 uses a different XMLHTTP object from IE 6
navigator.userAgent.indexOf('MSIE 5') >= 0 ?
'Microsoft.XMLHTTP' : 'Msxml2.XMLHTTP' );
};
//Create the request object
var xml = new XMLHttpRequest;




