biaoge's blog

以前端技术为主

Archive for the ‘前端技术’ Category

前端代码的本地化开发

with 4 comments

如果网站的后台是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再刷新看效果。

Written by admin

September 9th, 2011 at 12:02 am

Posted in 前端技术

Tagged with

优化HTML

with 7 comments

  1. 为何要保持标签整洁
  2. 一团糟的写法
  3. 附加的优化方法
  4. 激进的优化方法
  5. 物极必反
  6. 反面教材
  7. 工具
  8. 将来的考虑

为何要保持标签整洁

客户端的优化近来倍受关注,可是有些较基本的方面却被忽视。如果你仔细观察某些页面(即便是那些本来应该深度优化的页面),很容易就能在他们的标签中找到一大堆冗余的、不高效的结构。所有这些累赘给本来应该尽可能保持轻量级的页面增加了不必要的负担。

保持文档整洁的原因不一定是为了更快的加载速度,更是为了让我们的建筑能有一个结实而牢固的基础,整洁的标记意味着更好的可访问性,更方便的维护,更易被搜索引擎检索,更小的体积仅仅是保持文档整洁的一个附加属性,也是我们应该这样做的另一个理由。

这篇文章里面,我们来瞧瞧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.01HTML 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.getElementsByNamedocument.evaluatedocument.querySelectorAll等等)把name替换成id可能会把事情搞砸。还要记住document.anchors只返回带有name属性的元素

6.<!doctype html>

一年多以前,Dustin Diaz建议使用HTML 5文档类型,作为减少页面体积的一个途径。这不是一个重要的优化方式,但是如果你不在乎是否通过检验并且需要使页面尽可能小巧,使用<!doctype html>是一个可行的备选方案。测试表明这个奇特的文档声明可以触发大量的浏览器的标准模式。

激进的优化方法

如果你渴望更多的,这里还有一些比较极端的方法。有一些(如略去选填的标签)已经出现过一段时间了,还有些之前没出现过。尽管这些做法看起来有些唐突,但是它们都可以通过检验,如果你的页面是HTML的而不是XHTML的。但是你的页面是HTML的,不是吗?;)

  1. 去掉HTML注释
  2. 去掉/减少空格
  3. 省去选填的结束标签(<p><p>foo)
  4. 如果允许,去掉属性值的引号 (<p class="foo"><p class=foo>)
  5. 删除布尔类型的属性的选填值(<option selected="selected"><option selected>)
  6. 转移内联样式、内联脚本、和html事件属性 (如果不能删掉它们的话)
  7. 转移class和id(需要同步修改脚本和样式)
  8. 把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>&nbsp,或替掉换基于表格的布局也是人工优化很好的例子。

就这些优化工具而言,我希望在不久的将来看到更多的压缩工具,将页面体积缩减推向一个新的高度。

如果你知道更多优化HTML的方法,请分享出来。很乐意大家提问,提建议或者纠正我的错误。

本文翻译自Optimizing HTML

Written by admin

February 26th, 2010 at 10:06 pm

this关键字

with 5 comments

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

Written by admin

January 10th, 2010 at 1:09 am

知名站点js库使用情况统计

with one comment

jQuery Prototype YUI Mootools
微软
新版MSN
诺基亚
美国在线
Google Code
维基百科
W3C
戴尔
IBM
Digg
Mozilla.org
wordpress.org
以及wordpress博客后台
twitter
Netflix
alexa.com
ESPN
亚马逊
Box.net
QQ校友
QQ群
改版后的新浪NBA频道
豆瓣
土豆
Apple
CNN
Ajaxian
Adobe
37signals
开心网
优酷
Yahoo
mozilla.com
美味书签
WebQQ
淘宝
支付宝
口碑网
阿里中文站
阿里国际站
沃尔玛
Paypal
linkedin
phpMyAdmin
CNET
必应
wufoo.com
Ask.com
About.com

开源js库在这些顶尖站点中的使用率如此之高,可见一定有其过人之处。顺便推荐火狐的Library Detactor插件,在firefox的状态栏显示正在访问的站点使用的是什么js库。

Written by admin

December 20th, 2009 at 1:27 am

Posted in 前端技术

Tagged with , ,

javascript的事件绑定函数

without comments

最早给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都有提供很方便的事件绑定接口,其实现的方式就复杂多了,当然功能也要强大得多。等有时间再研究。

Written by admin

December 13th, 2009 at 7:04 pm

Posted in 前端技术

Tagged with ,

谁说IE6不支持!important

with one comment

许多人认为ie6不支持!important,其实是被一条针对ie 6的css hack给误导了。

这条css hack是:

.test {
    height: auto !important;
    height: 500px;
}

.test的高度在其他浏览器里面是auto,而在ie6里面是500px,许多人在解释这条css hack之所以会生效是因为ie6不支持!important,误导了不少人。

其实ie6本身是支持!important的,下面换一种写法:

.test {
    height: auto !important;
}
.test {
    height: 500px;
}

发现ie6里面.test的高度也是auto,这说明ie6是支持!important的。那上面的hack之所以会生效是因为ie6的一个小小的bug,即当你把两条相同的声明放到同一个选择器里面的时候ie6才不认识!important。然而大部分时候,这个小小的bug并不影响我们在ie6内使用!important。

Written by admin

December 12th, 2009 at 11:27 pm

Posted in 前端技术

JS库开发原则

with 4 comments

1.保持无侵入式

html代码不必关心你的javascript在干什么。

2.严禁使用Object.prototype

这一条是如此重要以至于它有资格单独成为一条原则,对象是javascript最基本的构建元素,别把他弄乱了。

3.不要过度扩展

对javascript的内建对象的扩展越少越好。别误会,内建javascript对象本身有用的方法很少,你可能觉得有必要增加自己的一两个方法, 但是一两个对一个有创造力(js库)的程序员来说是不够的,停下来,只增加你真正想要的。越少去扩展内建对象,你与其他框架发生冲突的可能性就越少。

4.紧随标准

作为一个js库开发者,你在为javascript代码建立模式,然而模式在编程语言中意味着差劲,记住,关于javascript和DOM新 标准在不停地修订中,如果你打算去“修正”某些东西,那么先看看那些东西是否已经被修正过了。参考一下已有的解决方案。一旦跟随标准,请别掉队(比如,在 forEach方法中一个参数也不要漏掉)

5.或者跟随领袖

Mozilla引领javascript的潮流。该语言的发明 者,Brendan Eich仍然在改进它。js语言的新特性总是在Mozilla浏览器中最先出现,如果你打算给javascript语言增加新特性,请参考 Mozilla标准先。例如,如果你想增加一个遍历数组的方法,那么请把这个方法命名为forEach而不是each。而如果你是在补全缺失的功能,则要 严格参照现有标准(参见上一条)。

6.灵活一点

如果我想在不动你的js库源代码的情况下更改某个方法的行为,是否足够简单?如果还不够简单就让它更简单一点吧。

7.管理好内存

人们都在乎内存泄漏。把它做好。

8.去除浏览器检测

似乎浏览器厂商在增加新特性的竞争上永无休止。作为js库开发者,你应该紧跟最新的潮流,你不应该仅仅是偶尔看一下Ajaxian,你应该不知疲倦地阅读每一篇blog以便获知最新的hack,浏览器检测会让你越陷越深。

9.越小越好

许多js库已很成熟,其中一些已经被一些很重要的网站所采用。但不是每个人都在用2M的DSL宽带,所以保持你的js库小巧。最好能提供一个build页面让我能够快速的根据我的需要定制出我的js库。

10.第十条

很棒第十条,你总能信赖这一条。第十条是:可预见性。我应该可以根据方法的名字猜到它是干什么的,同样地,如果记不起一个方法的名字,我也应该可以猜得到。

11.附加的几条

1)文档,虽然讨厌但意义重大。

2)多用名字空间,这样我才不至于打电话骚扰你。

3)记住数以百万计的用户潜在用户可能会运行你的代码。

顺便声明,base2没有更改任何内建的javascript对象。

本文翻译自Rules For JavaScript Library Authors,这是Dean Edwards在开发base2时候的一些体会,对于在开发自己的js库的同学应该有较强的借鉴意义。Dean Edwards是公认的javascript高手,jQuery的作者Jhone Resig也很欣赏他。

Written by admin

December 8th, 2009 at 8:19 pm

Posted in 前端技术

Tagged with

新版本的IE collection——目前最好的多版本IE并存方案

with 4 comments

一直找不到比较完美的多版本IE并存的方法,因为IE 8的出现,它的developer toolbar成为必备的调试工具,然而multiple IE的IE 6无法和ie 8并存,在装有ie 8的机器上装multiple IE的话,IE 6内无法选择文本,并且不支持cookie,而IEtester尽管勉强可用,但是同样不支持cookie,所以无法登陆,调试的意义也就不大。

上一个版本的IE collection 1.4.2.0将各种版本的IE集成在一个安装包内,但是bug太多,可用性实在是很差,并且它的IE8自带的调试工具是不可用的,前段时间给作者发了一个feature request,希望他能让修好IE8的developer toolbar,前两天收到答复,他已经将IE collection升级到了1.5.0.0版本,立即很兴奋地下下来测试,当时在公司用自带IE6的xp sp3,安装完毕之后ie 8的toolbar是可用了,但是IE6又坏掉了,总之bug还是较多,后来为了修好IE,又把系统默认的浏览器升级到了IE 8,重新测试ie collection,这次发现它和IE 8能很完美的共存,IE6,IE7的文字高亮和输入框获取焦点都没有任何问题。

所以目前最好的多版本IE并存方案是:将系统自带的IE升级到IE8,然后安装IE collection 1.5.0.0,记得不要在系统自带浏览器版本为IE 6的情况下安装,否则会遇到很多问题。

Written by admin

July 11th, 2009 at 5:43 pm

Posted in 前端技术

预载:hover背景图

without comments

我们经常会遇到鼠标划过链接时需要改变背景图的情况,其实做起来挺简单,就是给链接的:hover伪类设置一张背景图。但这是问题便出现了,设置在:hover下的背景图并没有随html文档一同被载入,而是在我们把鼠标放在链接上时才开始像服务器发出请求。

例如,如果我们在一个a链接上写了以下样式:

a.test {
    background: url(images/bgImg.png) no-repeat;
}
a.test:hover {
    background: url(images/bgImgHover.png) no-repeat;
}

那么当用户把鼠标移到a链接上时,原先设置在a上的背景图消失了,而设置在:hover上的背景图bgImgHover.png又正在载入,这是就会出现没有背景图的瞬间,这种用户体验是很糟糕的,特别是当背景图比较大时,可能还不止是一瞬间,用户需要等上一段时间才能见到背景图。

解决这个问题的办法是预载背景图,就是说强迫设置在hover上的背景图随HTML文档一起被载入,这样当用户的鼠标放到链接上时:hover上的背景图已经被下载到本地,也就不会出现上述的情况。下面提供几种方法:

方法一:采用CSS Sprite技术

CSS sprite技术是一种将许多小背景图合成到一张大图以减少http连接数从而提升页面载入速度的技术。用过YSlow的人都知道Yahoo把减少HTTP连接数列为提高网站速度效率的第一要素。但此处采用sprite是为了解决预载:hover背景图的问题,将:hover背景图与任意一张非hover、非active、非focus下的背景图合成到一张图片,这样图片就会随html文档一起被载入。

方法二:将需要预载的背景图设置在文档内任意一个元素上,并通过backgroud-position让其不可见

文档内有如下html代码:

	<li class="test"><a href="#">test</a></li>

css代码如下:

.test {
	/* 把li的背景设置成这张需要预载的图片,但是让其不可见 */
	background:url(images/bgImgHover.png) no-repeat -999px -999px;
}
.test a {
    background:url(images/bgImg.png) no-repeat;   /* 正常状态下的背景图 */
}
.test a:hover {
    background:url(images/bgImgHover.png) no-repeat;       /* hover状态下的背景图 */
}

由于在li上设置了bgImgHover.png这张背景图,尽管我们看不到它,但是图片在文档载入之前就已经下载到了本地。

方法三:用JavaScript预载图片

有些时候假如我们需要预载的图片有十几张,并且由于种种原因我们无法使用Sprite的时候,用JavaScript便成为更好的选择。

<script type="text/javascript">
    for ( var i = 1; i <= 15; i++ ) ( new Image() ).src = './images/bgImg' + i + '.png';
</script>

这样会预载从bgImg1.png,bgImg2.png…直到bgImg15.png。

Written by admin

January 8th, 2009 at 11:30 pm

Posted in 前端技术

Tagged with , , , ,

《精通javascript》中的几个错误

with 4 comments

第86页,代码清单5-28中有这样一段:

//向后遍历数组,
//因为我们向前添加元素
for ( var i = elems.length - 1; i >= 0; i-- ) {
    parent.insertBefore( elems[i], before );
}

此处用一个反循环反而把被添加元素的顺序搞反了,用一个正循环才能得到正确的顺序。

另一个错误:在103页,表单事件那部分有这样一句:“select事件在<select>元素更新后触发”。事实上,<select>元素更新后只会触发onchange事件,不会触发onselect事件,参见这里:“Broadly speaking, buttons can generate click events, and text and select items can generate focus, blur, select, and change events. The one potentially confusing aspect of this organization of events is that selection lists cannot generate the select event. This is because they have no editable text. ”

Written by admin

November 16th, 2008 at 12:08 pm

Posted in 前端技术

Tagged with , , ,